PS:以下题目均在BUUCTF
1、极客大挑战 2019 EasySQL(万能账号密码,SQL注入,HackBar)
题目

做法
启动靶机,打开给出的网址



输入1、1’、1″判断SQL语句闭合方式





注:以下sql语句如’1″ ‘ 实际上是’1″‘ ,这里只是为了快速区分
(1)一般情况下,SQL语句闭合方式为单引号
当用户名为1时,形成的sql语句是 select * from table_name where username=’1′ and password=’123′;
当用户名为1″时,形成的sql语句是正确的 select * from table_name where username=’1″ ‘ and password=’123’;
当字符串内需要包含双引号时,除了使用转义字符外,也可以使用一对单引号来包括字符串。此时字符串内的双引号被视为普通字符,无需特殊处理
同理,当字符串内需要包含单引号时,除了使用转义字符外,也可以使用一对双引号来包括字符串。此时字符串内的单引号被视为普通字符,无需特殊处理
补充
使用(转义字符)来判断SQL注入的闭合方式 原理:当闭合字符遇到转义字符时,会被转义,那么没有闭合符的语句就不完整了,就会报错,通过报错信息我们就可以推断出闭合符。
分析报错信息:看\斜杠后面跟着的字符,是什么字符,它的闭合字符就是什么,若是没有,则为数字型。 (但是在本题中有两个变量,这个方法不太适用)
当用户名为1’时,形成的sql语句是错误的 select * from table_name where username=’1′ ‘and password=’123’;
第一个单引号和第二个单引号形成了新的闭合,剩余第三个单引号,组成的sql语句不正确,于是语句报错
因此,SQL语句闭合方式是单引号
(2)假设MySQL语句为双引号闭合
username输入1时,形成的sql语句是正确的 select * from table_name where username=”1″and password=”123″;
username输入1″时,形成的sql语句是 select * from table_name where username=”1″ “and password=”123”; 正确的SQL语句不可以出现一对双引号包含双引号的。所以上面这条应该出现SQL报错,但实际没有报错,因此我们假设的双引号闭合方式是不成立的
username输入的是1’,形成的sql语句是正确的,不会报错 select * from table_name where username=”1′ “and password=”123”; 而然实际上这条语句报错了,因此我们假设的双引号闭合方式是不成立的
综上,我们可以推出SQL语句闭合方式是单引号
进行SQL注入
万能账号密码获取入口: 万能账号密码使用详解,渗透测试常用的入门级操作 – 知乎
由上得:该数据库的闭合方式为单引号
因此,在理解完以上网页内容后,我们回归该题
当我们不知道用户的账号并且不知道用户的密码时,可以使用万能账号 我们随便挑选一个——a or true #
但是它上面的万能账号都没有加单引号’或是双引号”
因此,综上,我们需要在a后面加上本题的闭合符号,“ ‘ ” 得出我们所需的账号为a’ or true #
密码随便输即可(但是看到其他人通常这种情况都直接写一样的,不知道有啥玄机在里面,这里注意一下)


补充做法(使用HackBar,不过跟上面的大差不差)
自行下载HackBar V2
前提: 先看测试后的网址(与为刚从靶机点进来的网址进行对比),可以看到我们输入的账号密码都显示在url中,可知此处是get传参
补充:get传参——参数和 URI 之间用问号?隔开, 参数键值用等号=连接,然后参数之间用连接符&拼接起来




username=后面跟的是账号
password=后面跟的是密码
因此,我们根据上面说的思路,把万能账号填进去,密码随便填,构造一下,复制
check.php?username=a' or true %23& password=1
然后填进Load URL得出的网址后面,点击Execute执行
解释:%23是#的 URL 编码形式,表示普通字符#, 在 URL 中,某些字符有特殊含义,需要使用URL 编码(即百分号编码)来表示, 类似于进制统一一样,为了避免不必要的麻烦,我们通常都把格式统一一下,
以下字符在 URL 中可直接使用,无需编码:
字母:a-z、A-Z
数字:0-9
部分符号:-、_、.、~
但是遇到特殊字符的时候,最好去查一查看下要不要转变成URL
得出flag

2、极客大挑战 2019 Havefun(简单分析,HackBar)
题目

做法
启动靶机,点进去


<!--
$cat=$_GET['cat'];
echo $cat;
if($cat=='dog'){
echo 'Syc{cat_cat_cat_cat}';
}
-->
简要分析一波
前提小知识
$cat $:表示一个自定义的普通变量 $+字符串:表示一个变量名/对象名 双$:表示一个可变变量,用于存储变量的值
$_GET $_GET 是 PHP中预定义的 超全局变量(Superglobal Variable),用于获取通过 HTTP GET请求发送的参数。 数据类型:本质是一个 关联数组,键为参数名,值为参数对应的值
说人话:如题中的
$cat=$_GET['cat'];
就是定义一个叫cat的变量,然后通过 $_GET 从URL参数中获取cat变量的值
echo $cat: echo PHP中有两个基本的输出方式:echo和print echo和print的区别: 1、echo – 可以输出一个或多个字符串 2、print – 只允许输出一个字符串,返回值总为 1 注: echo 是一个语言结构,使用的时候可以不用加括号,也可以加上括号: echo 或 echo()
Syc{cat_cat_cat_cat},类似于我们要的flag格式flag{},猜想就是满足上面的条件即可得出我们要的flag
$cat = $_GET['cat']; // 从URL参数中获取cat变量的值
echo $cat; // 直接输出cat变量的值(存在XSS风险)
if($cat == 'dog'){ // 判断cat变量是否严格等于字符串'dog'
echo 'Syc{cat_cat_cat_cat}'; // 如果条件成立,输出flag
}
方法一
直接在URL上编写payload 根据上面的分析,我们直接在地址后面加上
/?cat=dog
然后回车,我们就可以得出flag

解释:
(1)
不带斜杠:服务器不知道你到底要访问首页,还是整个站点的根? 让其误解路径,导致参数传不到代码里 带斜杠:就是访问站点的根路径,然后还写入了cat=dog的参数
(2)
- 满足
$_GET接收逻辑:必须用?参数名=值的格式,代码才能通过$_GET['cat']拿到值。 - get传参——参数和 URI 之间用问号
?隔开, 参数键值用等号=连接,然后参数之间用连接符&拼接起来方法二(思路大差不差,不过利用了一下HackBar)
F12后点击HackBar V2跳出此页面
然后点击左边的Load URL即可把当前的URL放进框中
然后就像上面的思路一般,直接在后面加入
/?cat=dog
点击Execute,即可得出flag

最后,回到题目提交flag即可

3、ACTF2020 新生赛 Include(phpfilter伪协议)
题目

做法
启动靶机,点进去


?file=flag.php
说明存在文件包含,原理是php://filter 协议
当它与包含函数结合时,php://filter流会被当作php文件执行。
用php://filter加编码,能让PHP把文件内容当普通文本返回(不执行PHP代码),这样就能拿到文件源码 ,实现任意文件读取。(这也是后面解释为什么要加base64编码的原因)
就是正常包含PHP文件执行时,代码会被执行,我们就看不到我们想要的东西 而通过php://filter,我们就可以把代码当做文本返回,而不是被执行,这样我们就可以得到我们想要的东西了
至此,我们就可以构造payload
file=php://filter/read=convert.base64-encode/resource=flag.php
解释: 1.**`php://filter
- PHP 内置的数据流过滤器协议,允许在文件读写时对内容进行转换。
read=convert.base64-encode- 过滤器参数:将读取的文件内容进行 Base64 编码后返回。
resource=flag.php- 目标文件:指定要读取的文件路径(这里是
flag.php)。
- 目标文件:指定要读取的文件路径(这里是
`php://filter是格式
read 这里是可选参数,有read和write,字面意思就是读和写,如果不写,那么网页会自动匹配一个合适的read或write:
convert.base64-encode 是过滤器。主要有四种:字符串过滤器,转换过滤器,压缩过滤器,加密过滤器。filter里可以用一或多个过滤器(中间用|隔开),这也为解题提供了多种方法,灵活运用过滤器是解题的关键。这里的过滤器是把文件flag.php里的代码转换(convert)为base64编码(encode)
resource** 是必选参数,后面写你要处理的文件名
URL修改后得出结果

复制下来,去解码,这里推荐一个(信息源于互联网,风险自担) Base64 编码/解码 – 锤子在线工具


4、HCTF 2018 WarmUp(代码审计)
题目

做法
启动靶机,点进去



1. 代码起始和高亮显示
<?php
highlight_file(__FILE__);
<?php:PHP 代码的起始标记。highlight_file(__FILE__):highlight_file()是 PHP 内置函数,用于高亮显示文件的源代码(以 HTML 格式展示)。__FILE__是 PHP 的魔术常量,表示当前文件的完整路径。- 这行代码会把整个 PHP 文件的源代码显示在浏览器中(通常用于调试)。
2. 定义类和检查函数
class emmm
{
public static function checkFile(&$page)
{
class emmm:定义一个名为emmm的类(类似 Java/C++ 中的类)。public static function checkFile(&$page):public:函数可以被外部访问。static:函数属于类本身,无需创建对象即可调用(如emmm::checkFile())。- 静态方法
checkFile用于检查文件 &$page:引用传递参数,直接修改原始变量的值(这里未实际修改)。
3. 白名单设置
$whitelist = ["source"=>"source.php","hint"=>"hint.php"];
- 创建一个关联数组(类似 Python 的字典),白名单中只允许两个文件:
source.phphint.php
4. 检查参数是否合法
if (! isset($page) || !is_string($page)) {
echo "you can't see it";
return false;
}
isset($page):检查变量$page是否存在且不为NULL。is_string($page):检查变量$page是否为字符串类型。- 如果
$page不存在或不是字符串,输出错误信息并终止检查。
5. 第一次白名单检查
if (in_array($page, $whitelist)) {
return true;
}
in_array($needle, $haystack):检查$needle是否在$haystack数组中。- 如果
$page直接等于source.php或hint.php,允许访问。
6. 截取问号前的内容再检查
$_page = mb_substr(
$page,
0,
mb_strpos($page . '?', '?')
);
if (in_array($_page, $whitelist)) {
return true;
}
mb_strpos($page . '?', '?'):mb_strpos()查找字符串中第一次出现?的位置。$page . '?'在字符串末尾添加?,防止找不到?时返回NULL。
mb_substr($page, 0, ...):截取$page从开头到?之前的部分。- 例如:
$page = "source.php?xxx"→$_page = "source.php"。 - 如果截取后的内容在白名单中,允许访问。
7. URL 解码后再次检查
$_page = urldecode($page);
$_page = mb_substr(
$_page,
0,
mb_strpos($_page . '?', '?')
);
if (in_array($_page, $whitelist)) {
return true;
}
urldecode($page):对 URL 编码的字符串进行解码(例如%20→ 空格)。- 再次截取解码后的字符串中
?之前的部分,检查是否在白名单中。 - 例如:
$page = "source.php%3Fxxx"(%3F是?的 URL 编码)→ 解码后$_page = "source.php"。
8. 检查失败处理
echo "you can't see it";
return false;
}
}
- 如果所有检查都不通过,输出错误信息并返回
false。
9. 主程序:接收用户输入并包含文件
if (! empty($_REQUEST['file'])
&& is_string($_REQUEST['file'])
&& emmm::checkFile($_REQUEST['file'])
) {
include $_REQUEST['file'];
exit;
} else {
echo "<br><img src=\"https://i.loli.net/2018/11/01/5bdb0d93dc794.jpg\" />";
}
?>
$_REQUEST['file']:获取用户通过 URL 参数(如?file=xxx)或 POST 表单提交的file参数。empty():检查变量是否为空(值为NULL、''、等)。- is_string( ):检查变量是否为字符串类型。
emmm::checkFile($_REQUEST['file']):调用之前定义的检查函数。include $_REQUEST['file']:include是 PHP 的文件包含函数,将指定文件的内容嵌入到当前脚本中执行 即先暂停主脚本的运行,先运行子脚本 (1)若内容是php的话,直接运行此php文件,完成后再跳出来继续运行主脚本(假设include里的脚本为子脚本) (2)若里面含有echo等输出函数、被引入文件包含 HTML 或非 PHP 内容(eg.纯文本内容)、return、被引入文件包含错误或警告信息等,则会直接读取并输出相应内容- 风险:如果检查函数存在漏洞,攻击者可能通过构造特殊的
file参数读取任意文件。
exit:终止脚本执行,防止后续代码被执行。- 如果条件不满足,显示一张默认图片。
综上,我们为了绕过上面的保护,只需要在URL后写入上面提到hint.php(自行尝试,source.php那个我试了不行,没有提示我们要的flag在哪)

至此,我们编写payload
source.php?file=hint.php?/../../../../ffffllllaaaagggg
source.php?file=source.php?/../../../../ffffllllaaaagggg
解释:
1、为什么php后要加“ ?”呢:
(1)为了应付上面的检查,不然它就会在ffffllllaaaagggg后面加一个?我们就无法通过检测 (2)为了应付include函数,如下
2、include函数解释(以下通过上面给的第一个payload加以解释)
第一步:绕过白名单
传进 file= 的是:
hint.php?/../../../../ffffllllaaaagggg
PHP 会提取 ? 前的部分(hint.php),发现它在白名单中,就放行了。
第二步:PHP include 怎么处理这个路径?
include('hint.php?/../../../../ffffllllaaaagggg');
这是 PHP 的语法。
PHP 遇到
include("xxx?yyy")的时候,只会 includexxx这个文件,不会理会?yyy。 所以,PHP 实际执行的是:include("hint.php");
到这里你是读取 hint.php 并执行它了!
第三步:hint.php 的作用
虽然你没看到 hint.php 的源码,但你可以合理推测它会这样写:
<include($_SERVER['QUERY_STRING']);// 它把 ? 后面的东西当成路径去 include>
你不知道你要找的flag在哪个文件里,一定会在这俩白名单文件里吗?谁也说不准,最简单的方法就是回到根目录进行查找了
你传的这个:
source.php?file=hint.php?/../../../../ffffllllaaaagggg
会让 hint.php 里的 $_SERVER['QUERY_STRING'] 变成:
/../../../../ffffllllaaaagggg
于是 hint.php 执行了:
include("/../../../../ffffllllaaaagggg"); // 最终解析为 /ffffllllaaaagggg
第二次 include() 是在 hint.php 中执行的,相对路径会从 hint.php 所在的目录 /var/www/html 开始解析,层层 ../ 向上,最终到达根目录 /,再拼上目标文件名,形成有效路径。
详细一点: 这里的include是这样工作的——假如hint.php的绝对路径是/var/www/html/hint.php,然后../就会变成/var/www/html,再../就会继续返回上一级/var/www,以此类推,最终返回根目录,即是/ffffllllaaaagggg了,就像你直接在你的c盘找一个文件一样,你不知道它放在哪,因此直接返回根目录一搜,即可搜到,因此,其实上面的payload有多少个../都没什么问题,但是一定不能少,根目录再上一级还是停留在根目录上,不会再改变
那还有一个问题,就是payload里的../最前面的/还有必要加吗?
我这边尝试了一下,发现去掉这个/还是可以得出flag,大概是无所谓的
但这里建议还是带上为好,因为带 / 更安全更规范,防止被解析成查询参数而不是路径字符串,从而导致我们得不到flag而去怀疑自己思路,导致更进一步的错误
3、如果没有那个 / 会怎样?(URL上需要加/)
如果你传:
hint.php?../../../../ffffllllaaaagggg
PHP 会以为你传的是个 GET 参数名叫 ../../../../ffffllllaaaagggg,值为空:
在不同 PHP/服务器组合中,有的解析器只有以 / 开头才会处理为路径
因此,这里强烈建议加上/,这里指的是URL上的
| 类型 | 意义 |
|---|---|
| 路径 | 控制服务器访问哪个资源 |
| 参数 | 把值传给正在运行的脚本 |
要是识别成参数,就变成是写进去东西了,而不是访问/找某个文件,跟我们的目的相悖了
4、但在include函数上呢,则恰恰相反了,是不建议加/的
它截取的是payload的?后面的,刚好把/截取到 一般来说,有/的是绝对路径,没有/的是相对路径
假设你的文件在这里:
/var/www/html/hint.php
你想 include:
/flag
那你可以这样:
include("../../../../flag"); // 从 hint.php 的目录出发 // 即:/var/www/html/../../../../flag = /flag
你也可以:
include("/flag"); // 绝对路径
都没问题。
但是你如果写:(可以,但没必要)
include("/../../../../flag"); // 从根目录开始,还向上跳❓
PHP 实际等价于:
/../../../../flag => /flag
虽然结果一样,但这写法:
- 没有逻辑意义:根目录不能再往上跳
- 容易引起误解或报错
- 在不同系统/配置上行为不一致(特别是 open_basedir 或安全策略存在时)
5、那还有一个问题,就是既然上面说的
source.php?file=hint.php?/../../../../ffffllllaaaagggg
和
source.php?file=hint.php?../../../../ffffllllaaaagggg
都可以得出flag,那我直接
source.php?file=hint.php?/ffffllllaaaagggg
行不行呢,上面说了嘛,既然直接从根目录出发这样写都可以,那我何必还要写这么多../呢 直接/,一步到位,岂不美哉?
但答案是不行的,至少在该题中找不到flag
❌ include("/flag"):
大概是路径解析过程中触发了安全限制/被限制了,因此行不通
✅ include("/../../flag"):
一般是失败的,跟上面一样 (/../../flag其实跟/flag一样的,”/”继续往上还是”/”因此后面的../其实没什么作用)
但如果路径被错误解析,则可能绕过限制(就像最前面没加/的情况一般) 因此,这种情况行不通的情况下,我们就得去掉最前面的/了
一旦路径以 / 开头,就告诉 PHP:
“我给的是绝对路径,不用管当前文件在哪。”
some/path/to/file→ 相对路径(从当前工作目录出发)/some/path/to/file→ 绝对路径(从根/出发)
没办法了,找了半天,最合理的解释也只能是这样了【单纯指的include(“/flag”)和include(“/../../flag”)】,平时做题/比赛的时候不行就按照上面的思路一个一个慢慢试吧
得出flag

复制,回去提交





