Closed
Bug 448611
Opened 16 years ago
Closed 14 years ago
File upload can be forged using a textarea
Categories
(Core :: Security, defect)
Tracking
()
RESOLVED
FIXED
Tracking | Status | |
---|---|---|
blocking2.0 | --- | final+ |
People
(Reporter: ma1, Assigned: sicking)
References
(Blocks 1 open bug)
Details
(Keywords: html5, Whiteboard: [sg:low][need decision from dveditz])
Attachments
(1 file, 1 obsolete file)
41.69 KB,
patch
|
smaug
:
review+
|
Details | Diff | Splinter Review |
Bug 442010 can be exploited the other way around, i.e. uploading the content of a textarea as it was a file by injecting a content-type like this:
<form enctype="multipart/form-data" method="post" action="http://some.cms.com/upload_web_page.php">
<textarea name='pagefile"; filename="forgedFileName.html
Content-Type: text/html; '>
forged content
</textarea>
</form>
<script>document.forms[0].submit()</script>
One possible attack scenario is using CSRF against a CMS allowing page uploads in order to inject a malicious web pages.
Reporter | ||
Comment 1•16 years ago
|
||
OK, I read more carefully bug 442010 and that's definitely not the same bug as this, albeit related.
Here the insertion point is the *field* name, rether than the file name, making it suitable for cross-site attacks.
Reporter | ||
Updated•16 years ago
|
OS: Windows Server 2003 → All
Comment 2•16 years ago
|
||
I just verified that Internet Explorer does exactly the same thing as us, the resulting POST data looks like this:
-----------------------------7d82ab2b1660736
Content-Disposition: form-data; name="pagefile"; filename="forgedFileName.html"
Content-Type: text/html; "
forged content
-----------------------------7d82ab2b1660736--
Opera however will escape double quotes:
------------ZsAplNTq9gnW1lGO0F8093
Content-Disposition: form-data; name="pagefile\"; filename=\"forgedFileName.html\"
Content-Type: text/html; "
forged content
------------ZsAplNTq9gnW1lGO0F8093--
Of course you can do this kind of thing with XMLHttpRequest already but it is restricted by the same-origin policy. Faking file upload with regular forms allows CSRF as well as XSS if the site doesn't validate the file name properly (which it often doesn't).
OS: All → Windows Server 2003
Comment 3•16 years ago
|
||
Checked this input against PHP interpreter - it accepts the data sent by Firefox as a file (obviously, there is nothing "wrong" with it). Opera's request is interpreted as a field named |pagefile";_filename="forgedFileName_html"| which is in line with RFC 822 (see quoted-string definition).
Comment 4•16 years ago
|
||
This looks like a public blog post from last February so there may not be much point in the security-sensitive flag.
http://kuza55.blogspot.com/2008/02/csrf-ing-file-upload-fields.html
Not sure how to rate the severity. It can't be used to hack Firefox itself or the user's machine, but it's definitely a potential csrf risk in the right circumstance.
Flags: wanted1.8.1.x+
Flags: blocking1.9.1?
Flags: blocking1.9.0.3?
Flags: blocking1.8.1.17?
Whiteboard: [sg:low]
Updated•16 years ago
|
Flags: blocking1.8.1.17?
Comment 5•16 years ago
|
||
Jonas: does this fall in your area?
Assignee: nobody → jonas
Flags: blocking1.9.0.3?
Flags: blocking1.9.0.3+
Flags: blocking1.8.1.18+
Assignee | ||
Comment 6•16 years ago
|
||
Yeah, it's well known that you can launch CSRF attacks using browsers. Server operators need to protect themselves using any of the available methods. You can read more about it here:
http://en.wikipedia.org/wiki/CSRF
This is very bad indeed, but hardly new information. Not really sure what we can do about it. I don't think forging file uploads are any more or less bad than anything else really. All the CSRF attacks that I have heard about so far hasn't needed file upload at all. For example banking transactions usually don't use file uploads.
Comment 7•16 years ago
|
||
How about doing the same thing as Opera - escaping double quotes in field names? Newline characters should be removed or replaced by spaces.
Assignee | ||
Comment 8•16 years ago
|
||
So both cross-site XHR as well as microsofts XDR is currently designed to allow POSTing arbitrary data with the Content-Type "text/plain", with the rationale that <form>s already allow this. (neither XHR nor XDR will allow the data to be read unless the page opts in, but at that point the request has already happened of course).
I think the fact of the matter is that for the vast majority of sites exploitable to CSRF the data it parses is simple <form> data. Simply because that is what it expects (it just doesn't expect it from cross site). So you don't have to get fancy with field names and the like, the browser will encode the data in exactly the format the server want.
So sure, we could put limits on what field names we send out, however it is unlikely to help many servers. And in order to be effective we'd have to put a stop to the XDR and cross-site XHR specs.
Or to put it another way: Have you ever encountered a server that would be helped by the remedies you are proposing? Or would we be making sacrifices for no real added value?
There are other proposals for how to help servers deal with CSRF. The prevailing idea right now is using the Origin header. However there are other bugs and forums that cover that topic.
Reporter | ||
Comment 9•16 years ago
|
||
Jonas, nobody proposed fixing this bug as a CSRF panacea, so I'm not sure of the reason why you're pointing general CSRF info and remedies like the Origin header here.
What we were suggesting is just that if a form is vulnerable to CSRF but not to "conventional" XSS (a common case, I suspect) and stores file uploads in a public area (e.g. a CMS), you could leverage an otherwise low-impact CSRF attack to inject persistent XSS pages or malware.
Without this bug, an attacker could not upload arbitrary "files" through CSRF, and this is what most people expects, I guess.
Comment 10•16 years ago
|
||
Jonas, I don't see how cross-site XHR is relevant here - files are sent with Content-Type: multipart/form-data and not text/plain. Note also that reflective XSS is impossible with XHR. As to which servers will be helped - quite a few. Above I tested the PHP interpreter and it processes escaped quotation marks in field names correctly. So removing line breaks and escaping quotes will immediately eliminate the CSRF/XSS threat for all PHP-based sites as far as file uploads go. Granted, that's a small improvement but it is an improvement.
Assignee | ||
Comment 11•16 years ago
|
||
The thing is that it seems like your proposal seems to fix a very narrow problem. It fixes CGIs that fulfill all of the following requirements:
1. Has no current CSRF protection (this is all too common).
2. Accepts file data.
3. Not exploitable unless file data is attached.
4. Accepts multipart/form-data encoded data.
5. Enforces the mimetype, i.e. is not exploitable when Content-Type is set to
"text/plain".
6. Honors quotes in filenames (apparently PHP does this)
6. The user can't be asked to attach what to the user seems like a harmless file
(i.e. contains no data that the user thinks is private to him/her).
The cost is that we couldn't implement future specs that would allow setting the value of a <input type=file> control based on data provided by the script.
I guess i'm not exited about the arbitrary restriction for file data, while we allow all sorts of other data (including file data with the "wrong" Content-Type "text/plain").
Comment 12•16 years ago
|
||
Points 4, 5 and 6 should be met by all PHP-based sites. Note also that 4 follows directly from 2 (there is no other way to send files). Second point 6 :) isn't a real restriction - if the user attaches the file, the attack requires user interaction, and the attacker cannot control the file name (in particular, he cannot introduce special characters like <> or | in the file name, at least Windows won't accept file names like this). Even to upload a file with given content he has to ask the user to download that file to disk and attach it then, that's even less likely successful then.
So real restrictions are only 2 and 3 - what we already said before. Still, I think this is worth doing.
Assignee | ||
Comment 13•16 years ago
|
||
Even if we take the actions suggested here we're still just protecting against *forging* file controls, we wouldn't protect against *using* file controls.
I.e. you could just use a normal file control and kindly ask the user to attach a file which is harmless to the user, but which would exploit the server. I.e. you would be relying on users to be protecting the server.
Additionally, if we ever in the future allowed something like
myFileInput.value = myFileObject;
(where myFileObject is a File object gotten through input.files.item(0) or some such.) we would be back to allowing the same submissions that we're trying to protect from here.
Sure, I'd be fine with quoting filenames, this seems useful anyways since it allows filenames with quotes. But I don't think it'll actually protect any servers.
Assignee | ||
Comment 14•16 years ago
|
||
Sorry, that should be escaping quotes in field names, not in file names.
Assignee | ||
Comment 15•16 years ago
|
||
Not blocking unless we can show that this is significantly different than CSRF in general.
Flags: blocking1.9.1? → blocking1.9.1-
Updated•16 years ago
|
Flags: blocking1.9.0.5?
Flags: blocking1.9.0.4+
Flags: blocking1.8.1.19?
Flags: blocking1.8.1.18+
Updated•16 years ago
|
Flags: blocking1.9.0.5?
Flags: blocking1.9.0.5+
Flags: blocking1.8.1.19?
Flags: blocking1.8.1.19+
Assignee | ||
Comment 16•16 years ago
|
||
Dan: What's your take here. Is this really something we want to change?
Updated•16 years ago
|
Whiteboard: [sg:low] → [sg:low][need decision from dveditz]
Comment 17•16 years ago
|
||
Yes, we should fix this. Realistically it's not going to block a Firefox 2.0 release. The issue is public so I'm going to remove the security flag.
Group: core-security
Flags: wanted1.9.0.x+
Flags: blocking1.9.0.5+
Flags: blocking1.8.1.19+
Assignee | ||
Comment 18•16 years ago
|
||
Dan: really? Did you read my previous comments in this bug?
Comment 19•16 years ago
|
||
So it may, or may be not a security issue, it's still an RFC violation:
RFC822 3.4.4:
[...]A quotation-mark that is to be part of a quoted-string, a parenthesis that is to be part of a comment and a backslash that is to be part of either must each be preceded by the quote-character backslash ("\").[...]
Added html5 keyword for the compliance issue.
Keywords: html5
Comment 20•14 years ago
|
||
Chrome isn't affected by this bug as it URL encodes quotes and newlines in field names.
Assignee | ||
Comment 21•14 years ago
|
||
I'll try to fix this for FF4.
However note that using CORS and cross-site XMLHttpRequest, you can generate a multipart/form-data request to any server which includes any file contents. One way of doing this is to simply generate the encoded data yourself and do something like:
xhr = new XMLHttpRequest();
xhr.open("POST", "http://somewhere.com");
xhr.withCredentials = true;
xhr.setRequestHeader("Content-Type", "multipart/form-data");
xhr.send(myencodeddata);
The above should work in all browsers that support CORS, including safari and chrome.
So there is no way to fix the "forge" aspect here. What we can do is to fix the part where a weird filename accidentally messes up a encoded submission.
blocking2.0: --- → final+
Comment 22•14 years ago
|
||
Presumably, the CORS stuff would only work if the server set the appropriate (insecure) headers? i.e. the server would have to set:
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: True
Assignee | ||
Comment 23•14 years ago
|
||
No, you can always send the request, which obviously happens before any response headers have been inspected.
I would additionally expect specs to soon support the ability to set the value of a <input type=file> to a file provided by the page (*Not* to a path-name provided by the page obviously. That would allow reading arbitrary data from the users file system).
Assignee | ||
Comment 24•14 years ago
|
||
Lacks tests
Assignee | ||
Comment 25•14 years ago
|
||
Somewhat big patch, but mostly test changes. Found a couple of other instances where we don't behave like IE (text/plain newline encoding, and <textarea name="">) so fixed those as well.
Attachment #481153 -
Attachment is obsolete: true
Attachment #485237 -
Flags: review?(Olli.Pettay)
Comment 26•14 years ago
|
||
Comment on attachment 485237 [details] [diff] [review]
Patch to fix
>@@ -665,19 +662,24 @@ nsFSTextPlain::GetEncodedSubmission(nsIU
> escapedBody.Adopt(escapedBuf);
>
> path += NS_LITERAL_CSTRING("&force-plain-text=Y&body=") + escapedBody;
>
> rv = aURI->SetPath(path);
>
> } else {
> // Create data stream
>+ nsCString cbody;
>+ EncodeVal(mBody, cbody, false);
>+ cbody.Adopt(nsLinebreakConverter::
>+ ConvertLineBreaks(cbody.get(),
>+ nsLinebreakConverter::eLinebreakAny,
>+ nsLinebreakConverter::eLinebreakNet));
> nsCOMPtr<nsIInputStream> bodyStream;
>- rv = NS_NewStringInputStream(getter_AddRefs(bodyStream),
>- mBody);
>+ rv = NS_NewCStringInputStream(getter_AddRefs(bodyStream), cbody);
Could you explain this, and even add some comment.
>diff --git a/content/html/content/src/nsHTMLTextAreaElement.cpp b/content/html/content/src/nsHTMLTextAreaElement.cpp
>--- a/content/html/content/src/nsHTMLTextAreaElement.cpp
>+++ b/content/html/content/src/nsHTMLTextAreaElement.cpp
>@@ -905,17 +905,18 @@ nsHTMLTextAreaElement::SubmitNamesValues
> if (IsDisabled()) {
> return NS_OK;
> }
>
> //
> // Get the name (if no name, no submit)
> //
> nsAutoString name;
>- if (!GetAttr(kNameSpaceID_None, nsGkAtoms::name, name)) {
>+ GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
>+ if (name.IsEmpty()) {
> return NS_OK;
> }
This is a bit risky, but seems like input element does the same.
> nsresult nsLinebreakConverter::ConvertLineBreaksInSitu(char **ioBuffer, ELinebreakType aSrcBreaks,
> ELinebreakType aDestBreaks, PRInt32 aSrcLen, PRInt32* outLen)
> {
> NS_ASSERTION(ioBuffer && *ioBuffer, "Null pointer passed");
> if (!ioBuffer || !*ioBuffer) return NS_ERROR_NULL_POINTER;
>
>- NS_ASSERTION(aDestBreaks != eLinebreakAny, "Invalid parameter");
>+ NS_ASSERTION(aDestBreaks != eLinebreakAny &&
>+ aSrcBreaks != eLinebreakSpace, "Invalid parameter");
You have a tab here. Use spaces.
> PRUnichar* nsLinebreakConverter::ConvertUnicharLineBreaks(const PRUnichar* aSrc,
> ELinebreakType aSrcBreaks, ELinebreakType aDestBreaks, PRInt32 aSrcLen, PRInt32* outLen)
> {
>- NS_ASSERTION(aDestBreaks != eLinebreakAny, "Invalid parameter");
>+ NS_ASSERTION(aDestBreaks != eLinebreakAny &&
>+ aSrcBreaks != eLinebreakSpace, "Invalid parameter");
Ditto.
> nsresult nsLinebreakConverter::ConvertUnicharLineBreaksInSitu(PRUnichar **ioBuffer,
> ELinebreakType aSrcBreaks, ELinebreakType aDestBreaks, PRInt32 aSrcLen, PRInt32* outLen)
> {
> NS_ASSERTION(ioBuffer && *ioBuffer, "Null pointer passed");
> if (!ioBuffer || !*ioBuffer) return NS_ERROR_NULL_POINTER;
>- NS_ASSERTION(aDestBreaks != eLinebreakAny, "Invalid parameter");
>+ NS_ASSERTION(aDestBreaks != eLinebreakAny &&
>+ aSrcBreaks != eLinebreakSpace, "Invalid parameter");
And here.
> nsresult nsLinebreakConverter::ConvertStringLineBreaks(nsString& ioString,
> ELinebreakType aSrcBreaks, ELinebreakType aDestBreaks)
> {
>
>- NS_ASSERTION(aDestBreaks != eLinebreakAny, "Invalid parameter");
>+ NS_ASSERTION(aDestBreaks != eLinebreakAny &&
>+ aSrcBreaks != eLinebreakSpace, "Invalid parameter");
And here
r=me, assuming the first comment gets an explanation.
Attachment #485237 -
Flags: review?(Olli.Pettay) → review+
Assignee | ||
Comment 27•14 years ago
|
||
I'm not sure what you think is unclear in the first thing you commented on. I've added the following comment to the code:
// We do want to send the data through the charset encoder and we want to
// normalize linebreaks to use the "standard net" format (\r\n), but we
// don't want to perform any other encoding. This means that names and
// values which contains '=' or newlines are potentially ambigiously
// encoded, but that how text/plain is specced.
Assignee | ||
Comment 28•14 years ago
|
||
Checked in: http://hg.mozilla.org/mozilla-central/rev/f65bef71ac10
Smaug, please do let me know if the comment doesn't explain things. I'm happy to explain here or amend the comment.
Assignee | ||
Updated•14 years ago
|
Status: NEW → RESOLVED
Closed: 14 years ago
Resolution: --- → FIXED
Comment 29•14 years ago
|
||
(In reply to comment #25)
> Created attachment 485237 [details] [diff] [review]
> Patch to fix
>
> Somewhat big patch, but mostly test changes. Found a couple of other instances
> where we don't behave like IE (text/plain newline encoding, and <textarea
> name="">) so fixed those as well.
Are those in the spec? If not, could you please file bugs?
Comment 31•14 years ago
|
||
Do we need to fix this on branches too?
Assignee | ||
Comment 32•14 years ago
|
||
I don't actually think this is a security issue at all. There are already webstandards which allow you to generate Blobs which can then be submitted without the need for "forging".
The fix which landed here mostly aligned us as far as submission format with other browsers.
Comment 33•14 years ago
|
||
Can the blobs be submitted cross-site without preflights, etc?
Assignee | ||
Comment 34•14 years ago
|
||
Yes, CORS allow cross-site POSTs without preflight as long as you're staying within a small set of content-types (multipart/form-data is one of them), and don't set any other exotic headers.
Assignee | ||
Comment 36•14 years ago
|
||
A site that accepts files from users and performs "dangerous" actions on them, without any CSRF protection, is playing a very dangerous game.
Such as site relies on the fact that the user understands that if he goes to evil.com and picks a "dangerous" file in a filepicker, such a file can be used to attack bunnies.com.
I really wouldn't expect users to understand that.
You need to log in
before you can comment on or make changes to this bug.
Description
•