Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sanitizing payload filename #194

Merged
merged 3 commits into from
Oct 1, 2020

Conversation

citizenTwice
Copy link
Contributor

For review: proposed mitigation of path traversal vulnerability, as per separate discussion vie email with developer Christopher Broderick.

@VboxNick
Copy link
Contributor

Thanks for your contribution. Could you please add some units tests?

@citizenTwice
Copy link
Contributor Author

Hi Nick, will look into it asap

@VboxNick
Copy link
Contributor

I'm also not quite sure whether "path traversal" should be sneaked or a certain type of exception must be thrown. The exception might reveal the fact of an attack even keeping in mind that the training partner is known.

@uhurusurfa what do you think or prefer?

@citizenTwice
Copy link
Contributor Author

The vulnerability is currently exploitable for certain configs of MessageFileModule in config.xml so, the exploitability should definitely be addressed. Personally, I do like the idea of the of ringing the bell with a suitable exception. The most likely attack scenario would be a compromised trading partner rather than a malicious one. Another possibility is a bogus filename generated because of bugs or wrong settings at the TP's end, non trivial heuristics would be required to distinguish between this and an actual attack... but warning everybody that something is not right feels like the right thing to do.

@igwtech
Copy link
Member

igwtech commented Aug 23, 2020 via email

@VboxNick
Copy link
Contributor

@igwtech Why would you store a malicious file? Even its name has been "silently" sanitized. Somebody might want that an another system reads it or somebody will try to execute it somehow. To me, it makes no sense to such files.

Thus, I propose introduce kind of "filename sanitizer" interface which would be part of the configuration. For instance:

 <module enabled="$properties.module.MessageFileModule.enabled$"
              classname="org.openas2.processor.storage.MessageFileModule"
              filename="%home%/../data/$msg.sender.as2_id$-$msg.receiver.as2_id$/inbox/$msg.content-disposition.filename$-$msg.headers.message-id$"
              filename_sanitizer="org.openas2.support.RejectingFileNameSanitizer"
              header="%home%/../data/$msg.sender.as2_id$-$msg.receiver.as2_id$/msgheaders/$date.yyyy-MM-dd$/$msg.content-disposition.filename$-$msg.headers.message-id$"
              protocol="as2"
              tempdir="%home%/../data/temp"/>

or

 <module enabled="$properties.module.MessageFileModule.enabled$"
               classname="org.openas2.processor.storage.MessageFileModule"
               filename="%home%/../data/$msg.sender.as2_id$-$msg.receiver.as2_id$/inbox/$msg.content-disposition.filename$-$msg.headers.message-id$"
               filename_sanitizer="org.openas2.support.RenamingFileNameSanitizer"
               header="%home%/../data/$msg.sender.as2_id$-$msg.receiver.as2_id$/msgheaders/$date.yyyy-MM-dd$/$msg.content-disposition.filename$-$msg.headers.message-id$"
               protocol="as2"
               tempdir="%home%/../data/temp"/>

@uhurusurfa
Copy link
Contributor

Having reflected on this, I have reviewed the IETF AS2 spec and the related ones that it relies on for guidance.
The received filename should always be strippied of any path information.
The IETF related spec is section 2.3 here:
https://tools.ietf.org/html/rfc2183

Specifically this line:
The receiving MUA SHOULD NOT respect any directory path informationthat may seem to be present in the filename parameter.

So th e method should be changed to ensure all path information is always stripped. from the filename parameter along with any other characters that are deemed unusable for the target system.
It should invoke this method to ensure the filename itself is acceptable after path stripping: org.openas2.utils.IOUtil.cleanFilename()

An exception should only be raised if the filename includes path information and not if the filename itself contains reserved characters since different file systems have different requirements regardin reserved characters.

@citizenTwice
Copy link
Contributor Author

citizenTwice commented Aug 23, 2020

I think we can (mostly) leverage the JDK to implement the behavior outlined in Chris' comment.

Specifically

  1. Use a combination of FileSystem.getPath + catching InvalidPathException to detect illegal characters in filenames.
  2. Compare the proposed payload filename with the result of Path.getFileName() to detect directory paths embedded within it.

A quick look at OpenJDK suggests the existing implementations do a good job at dealing with OS-specific conventions&requirements, see the Windows and Unix getFileName implementations

as well as the Windows PathParser catching illegal chars:

// Reserved characters for window path name
    private static final String reservedChars = "<>:\"|?*";
    private static final boolean isInvalidPathChar(char ch) {
        return ch < '\u0020' || reservedChars.indexOf(ch) != -1;
    }

So, please review the below pseudocode:

// tmpFilename = filenamed obtained from payload
// Check for illegal chars
try tmpPath = fileSystem.getPath(tmpFilename)
if InvalidPathException thrown then
  log.warn("invalid chars in filename")
  tmpFilename = org.openas2.utils.IOUtil.cleanFilename()
endif
// Note the below could theoretically throw IPE again... Generate new filename in that case?
try tmpPath = fileSystem.getPath(tmpFilename) 

// No illegal chars, check for directory path in filename
baseName = tmpPath.getFileName()
if NOT baseName.equalsIgnoreCase(tmpFilename) then
    throw IllegalPayloadFilenameException("Payload filename contains directory path", tmpFilename)
endif
// fall-through, everything cool

@igwtech
Copy link
Member

igwtech commented Aug 23, 2020 via email

@citizenTwice
Copy link
Contributor Author

Hi Javier @igwtech

Yes, for 'own use', I always stick to that approach and restrict the name to [ A-Z 0-9 -_. ] only ,which is enough as far as I'm concerned, esp for EDI. But I'm not so sure about OpenAS2 as it is meant to be generally deployable/usable anywhere in the world. I think we can trust the JRE/JDK here: for path traversal protection I think Path.getFileName is good enough as well as InvalidPathException for catching any illegal characters for a given platform, given how wildly the requirements differ between the various OS, it is unlikely that we would do a better job than the JDK developers if we try to reimplement those checks...
Thanks

@igwtech
Copy link
Member

igwtech commented Aug 24, 2020 via email

@uhurusurfa
Copy link
Contributor

@citizenTwice - happy with that

@citizenTwice
Copy link
Contributor Author

@citizenTwice - happy with that

will get coding and add a new commit to this PR

added IOUtil.getSafeFilename, this is invoked from BaseMessage when assigning an inbound msg filename
@citizenTwice
Copy link
Contributor Author

citizenTwice commented Sep 30, 2020

@uhurusurfa @VboxNick @igwtech

Added new commits, please review.

Updates:

The filename sent by partner is now passed to IOUtil::getSafeFilename,
new function that works as follows:

1 - known, bad characters (e.g. 0x0d, 0xa, 0x00) are removed automatically
2 - if the platform-specific nio.file.Path still rejects the cleaned name
InvalidMessageException is thrown
3 - the name is checked for embedded directory paths
4 - if any are found
InvalidMessageException is thrown
5 - otherwise the (cleaned) filename is returned to the caller

I've also tried to add a few unit tests. This turned out to be a bit tricky for me because rejection of some filenames is platform-dependent and the tests need to be deterministic. I've opted to let the tests run only on the platforms where I was able to directly verify the test results myself, that's the three main desktop/server OS's (Mac,Linux,Windows) ...but still, I am unsure as to whether better solutions for handling such cases exist.

And sorry for the delay following-up on this.

@uhurusurfa uhurusurfa merged commit 76cacda into OpenAS2:master Oct 1, 2020
@uhurusurfa
Copy link
Contributor

Awesome - thank you for the contribution.

@adioss
Copy link

adioss commented Jun 19, 2023

@uhurusurfa sorry to bother you with that but, shouldn't a CVE being created associated to this issue?
Thanks a lot,
Adrien

@uhurusurfa
Copy link
Contributor

@adioss It was not a vulnerability

@adioss
Copy link

adioss commented Jun 19, 2023

@uhurusurfa thanks a lot for the feedback. In fact, to be 100% transparent, I'm an AppSec engineer and Snyk reports this as a security issue.

Adrien

@uhurusurfa
Copy link
Contributor

uhurusurfa commented Jun 21, 2023

@adioss - although that getSafeFielname() method was added to parse file names, there was already a filename check done in IOUtil.cleanFilename() that is called further downstream when the received file is actually persisted in the storage module handler and it is still called to this day so there was never a security vulnerability in OpenAS2.
What is claimed in Snyk is true that a filename could be contrived that in theory could do what the reporter "citizenTwice" claims but "citizenTwice" did not actually test that theory at all because if that claimant had, they would have realised that it was not and is not possible in OpenAS2.

@citizenTwice
Copy link
Contributor Author

citizenTwice commented Jun 21, 2023

@adioss @uhurusurfa just jumping in to confirm what Christopher said about the filename filtering logic.

However, a correction: I did 100% test the vulnerability with that version of OpenAS2, dropping a an arbitrary, executable payload in the bin/ directory.
What, in practice, made exploitation highly unlikely is that it would first require establishing a trading partnership a trusted party that would later have to either turn malicious or get compromised in a very specific way.

@uhurusurfa
Copy link
Contributor

@citizenTwice - Thanks for the clarification. I am surprised and will cross check that the exploit does work without the getSafeFilename() invocation.
@adioss - If true then I agree it should have been a CVE

@citizenTwice
Copy link
Contributor Author

citizenTwice commented Jun 22, 2023

@uhurusurfa @adioss just LMK if there's anything I can do to help, this was a while ago but I think I could probably dig up my notes & screenshots.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants