BUUCTF-Web-wp(1-4)
本文最后更新于 14 天前,其中的信息可能已经有所发展或是发生改变。

PS:以下题目均在BUUCTF

1、极客大挑战 2019 EasySQL(单引号闭合,万能账号密码,SQL注入,HackBar)

题目

做法

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

随便输点东西进去,测试一下

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

输入以上两个都是以下结果
但是,输入1’时,出现的是另外结果

输入1,1″时,SQL语句没有报错,只是提示我们输入的值是不对的,而输入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 注入的闭合方式

原理: 当参数后面的闭合符遇到转义字符 \ 时,闭合符会被转义,从而失去原本闭合字符串的作用

这样 SQL 语句中的字符串就无法正常结束,语句结构被破坏,数据库就会报错

通过观察被转义的字符,可以推断当前参数使用的闭合符

例如,假设后端 SQL 语句大致如下:

select * from table_name where username='$username' and password='$password';

当 username 输入 1\ 时,形成的 SQL 语句为:

select * from table_name where username='1\' and password='123';

此时,反斜杠 \ 会把它后面的单引号 ' 转义掉,导致原本用于闭合 username 字符串的单引号失效。这样 username 字符串没有正常闭合,后面的 SQL 语句结构就会被破坏,从而产生报错

也就是说,原本应该在 1 后面结束的字符串没有结束,而是继续向后匹配,直到遇到后面 password 部分中的下一个单引号 ' 才尝试闭合。

可以理解成类似下面这样:

username='1\' and password='

此时前面的字符串内容变成了:

1' and password=

而后面的 123' 就脱离了原本的 SQL 结构,导致整条 SQL 语句语法错误,从而产生报错

因此,如果报错信息中可以看到类似 1\' 的结构,说明反斜杠后面被转义的是单引号 ',可以推断 username 参数原本使用单引号闭合

也可以通过输入单引号进行验证。当 username 输入 1' 时,形成的 SQL 语句为:

select * from table_name where username='1'' and password='123';

其中,前两个单引号组成了一个完整的字符串:

'1'

但后面又多出了一个单引号 ',导致 SQL 语句结构不完整,从而报错

综上,可以判断本题中 username 参数使用的是单引号闭合

需要注意的是,本题中同时存在 username 和 password 两个变量,使用反斜杠判断时可能会影响后面 password 部分的引号结构,因此这种方法不一定非常直观,通常需要结合单引号、双引号、注释符等方式一起判断

(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 #

密码随便输即可

复制,回去题目提交flag

补充做法(使用HackBar,不过跟上面的大差不差)

自行下载HackBar V2

前提: 先看测试后的网址(与为刚从靶机点进来的网址进行对比),可以看到我们输入的账号密码都显示在url中,可知此处是get传参

补充:get传参——参数和 URI 之间用问号?隔开, 参数键值用等号=连接,然后参数之间用连接符&拼接起来

因为get传参为参数直接暴露在 URL 中,且网页文件名为check.php,只是处理登录校验的常见文件名,而非诸如 login.php(通过表单 POST 传参)等需要动用其他工具,则可直接使用HackBar 修改参数
点击启动靶机后给出的网址,弹出页面,按F12,选择HackBar V2(绿色图标那个)
按一下Load URL,即可获取当前页面的网址 然后随便输入用户名密码,进去后看看网址
我们直接看测试后多出的网址

username=后面跟的是账号

password=后面跟的是密码

因此,我们根据上面说的思路,把万能账号填进去,密码随便填,构造一下,复制

check.php?username=a' or true %23&password=1 

然后填进Load URL得出的网址后面,点击Execute执行

解释:%23是#的 URL 编码形式,表示普通字符#, 在 URL 中,某些字符有特殊含义,需要使用URL 编码(即百分号编码)来表示, 类似于进制统一一样,为了避免不必要的麻烦,我们通常都把格式统一一下,

以下字符在 URL 中可直接使用,无需编码: 字母:a-zA-Z 数字:0-9 部分符号:-_.~ 但是遇到特殊字符的时候,最好去查一查看下要不要转变成URL

得出flag

2、极客大挑战 2019 Havefun(HTML注释源码泄露,GET参数控制,HackBar)

题目

做法

启动靶机,点进去

常规F12看看源代码
发现这里有被注释了的代码

<!--
        $cat=$_GET['cat'];
        echo $cat;
        if($cat=='dog'){
            echo 'Syc{cat_cat_cat_cat}';
        }
        -->

简要分析一波

前提小知识

$cat

$:表示一个自定义的普通变量。

$ + 字符串:表示一个变量名 / 对象名。

$,也就是 $$:表示可变变量,用于通过一个变量的值去访问另一个变量。


$_GET

$_GET 是 PHP 中预定义的超全局变量(Superglobal Variable),用于获取通过 HTTP GET 请求发送的参数。

说人话:
如果 URL 是:

?cat=dog

那么 PHP 可以通过 $_GET['cat'] 获取到参数 cat 的值,也就是 dog

例如题目中的代码:

$cat = $_GET['cat'];

意思是:定义一个叫 $cat 的变量,然后通过 $_GET 从 URL 参数中获取 cat 的值,并赋值给 $cat


echo $cat

echo 是 PHP 中常用的输出方式,用于把内容输出到页面上。

例如:

echo $cat;

意思是:把变量 $cat 的值直接输出到页面。

PHP 中常见的两个输出方式是 echoprint

区别:

  1. echo 可以输出一个或多个字符串。

例如输出一个字符串:

echo "hello";

输出结果:

hello

例如输出多个字符串:

echo "hello", " ", "world";

输出结果:

hello world

也可以输出变量:

$name = "cat";
echo "hello ", $name;

输出结果:

hello cat

  1. print 只能输出一个字符串,并且返回值总是 1

例如:

print "hello";

输出结果:

hello

print 有返回值,所以可以这样写:

$result = print "hello";

输出结果:

hello

此时 $result 的值是:

1

但是 print 不能像 echo 那样一次输出多个字符串:

print "hello", " ", "world";

这种写法是错误的。

如果想用 print 输出多个内容,可以先拼接成一个字符串:

print "hello" . " " . "world";

输出结果:

hello world

补充:

echo 是 PHP 的语言结构,不是普通函数,所以使用时可以不加括号,也可以加括号:

echo $cat;
echo($cat);

题目代码分析

题目中的关键代码大概是:

$cat = $_GET['cat']; // 从 URL 参数中获取 cat 的值
echo $cat; // 直接输出 cat 变量的值
if ($cat == 'dog') { // 判断 cat 的值是否等于字符串 'dog'
    echo 'Syc{cat_cat_cat_cat}'; // 如果条件成立,输出类似 flag 的内容
}

解题思路

因为代码中有判断:

if ($cat == 'dog')

所以我们只需要让 URL 参数 cat 的值等于 dog 即可。

构造 URL 参数:

?cat=dog

这样:

$cat = $_GET['cat'];

就相当于:

$cat = 'dog';

条件判断成立后,页面就会输出:

Syc{cat_cat_cat_cat}

Syc{cat_cat_cat_cat} 的格式类似于常见的 flag 格式 flag{},所以可以猜测它就是本题要找的 flag。


注意点

这里的判断使用的是:

$cat == 'dog'

== 是弱比较,只判断值是否相等。

如果是严格比较,应该写成:

$cat === 'dog'

=== 不仅比较值,还会比较数据类型。


方法一

直接在URL上编写payload 根据上面的分析,我们直接在地址后面加上

/?cat=dog

然后回车,我们就可以得出flag

解释:

(1)关于 / 的作用

URL 的基本结构是:

协议://域名或IP/路径?参数名=参数值

例如:

http://example.com/?cat=dog

这里的 / 表示访问网站的根路径,也就是网站首页所在的位置。

也就是说:

http://example.com/

表示访问:

网站根目录 /

服务器通常会在根目录下自动寻找默认首页文件,例如:

index.php
index.html

所以在 CTF 题目里,如果题目的代码在首页逻辑中,比如:

$cat = $_GET['cat'];

那么我们访问:

http://example.com/?cat=dog

意思就是:

访问网站首页,并传入 GET 参数 cat=dog

而:

http://example.com?cat=dog

很多情况下也能访问,因为浏览器或服务器通常会自动把它理解成:

http://example.com/?cat=dog

也就是默认访问根路径 /

但是为了规范和清晰,建议写成:

http://example.com/?cat=dog

原因是:

  1. / 明确表示访问网站根路径。
  2. ?cat=dog 明确表示给这个路径传入 GET 参数。
  3. 写成 /?cat=dog 可以清楚区分“访问路径”和“传入参数”。
  4. 在写题解、payload、笔记时更标准,不容易产生歧义。

简单理解:

http://example.com/?cat=dog

可以拆成:

http://example.com   网站地址
/                    访问根路径,也就是首页
?cat=dog             传入 GET 参数 cat=dog

所以 / 的作用就是:

告诉服务器:我要访问网站根路径,也就是首页;然后再把 cat=dog 这个参数传进去。

再深入一点,拿我们这个现在访问的网页来举例,一样的道理

https://snk0xheart.top/buuctf-web-wp/

也要按同样的逻辑理解。

它可以拆成:

https://snk0xheart.top   网站域名
/buuctf-web-wp/          访问路径

这里的 /buuctf-web-wp/ 不是网站根目录本身,而是根目录下面的一个路径 / 目录

也就是说:

/                    网站根路径
/buuctf-web-wp/      根路径下面的 buuctf-web-wp 路径

如果要给这个页面传 GET 参数,就要把参数加在完整路径后面:

https://snk0xheart.top/buuctf-web-wp/?cat=dog

意思是:

访问 /buuctf-web-wp/ 这个路径,并传入 GET 参数 cat=dog

但是注意:不要乱测,否则容易被屏蔽甚至是更严重后果

(2)关于 GET 传参

GET 传参需要使用 ?参数名=值 的格式

参数和路径之间用问号 ? 隔开

PS: ? 在 URL 里表示:

路径结束,后面开始传 GET 参数

参数名和参数值之间用等号 = 连接

多个参数之间用 & 连接。

例如:

url/?cat=dog

表示传入一个参数:

cat = dog

如果有多个参数:

/?cat=dog&id=1&name=test

表示传入三个参数:

cat = dog
id = 1
name = test

在 PHP 中,可以通过 $_GET 获取这些参数:

$cat = $_GET['cat'];
$id = $_GET['id'];
$name = $_GET['name'];

方法二(思路大差不差,不过利用了一下HackBar)

F12后点击HackBar V2跳出此页面

然后点击左边的Load URL即可把当前的URL放进框中

然后就像上面的思路一般,直接在后面加入

/?cat=dog

点击Execute,即可得出flag

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

3、ACTF2020 新生赛 Include(文件包含,PHP filter伪协议,任意文件读取)

题目

做法

启动靶机,点进去

点进去

文件包含漏洞:php://filter 读取源码

查看 URL,发现有参数:

?file=flag.php

说明页面可能通过 file 参数控制要访问或包含的文件。

例如后端代码可能类似于:

include($_GET['file']);

或者:

require($_GET['file']);

这种情况下,用户可以控制被包含的文件名,就可能存在文件包含漏洞


为什么不能直接访问 flag.php

如果直接传入:

?file=flag.php

服务器会正常包含并执行 flag.php

如果 flag.php 里面是 PHP 代码,例如:

<?php
$flag = "flag{xxxxxx}";
?>

那么 PHP 代码会被服务器执行,而不是直接显示源码。

也就是说,页面不会把:

$flag = "flag{xxxxxx}";

原样显示出来。

所以我们需要想办法让 PHP 读取文件源码,而不是执行文件代码


php://filter 的作用

php://filter 是 PHP 内置的伪协议,也叫数据流过滤器协议。

它的作用是:
在 PHP 读取文件内容时,对文件内容进行一层处理。

这里我们使用:

convert.base64-encode

这个过滤器,把目标文件内容转换成 Base64 编码后再返回。

这样做的好处是:

原本 flag.php 会被当成 PHP 代码执行。

但是经过 Base64 编码后,文件内容变成了一串普通文本。

这样 PHP 就不会把它当成 PHP 代码执行,而是把编码后的内容显示出来。

然后我们再把 Base64 解码,就可以得到 flag.php 的源码。


构造 Payload

构造如下 payload:

?file=php://filter/read=convert.base64-encode/resource=flag.php

完整含义是:

通过 file 参数,让服务器使用 php://filter 读取 flag.php,
并在读取时把 flag.php 的内容进行 Base64 编码后返回。

Payload 结构解释

php://filter/read=convert.base64-encode/resource=flag.php

可以拆成三部分:

  1. php://filter
php://filter

这是 PHP 的数据流过滤器协议。

它可以在读取或写入文件时,对文件内容进行过滤处理。

在文件包含题里,常用它来读取 PHP 文件源码。


  1. read=convert.base64-encode
read=convert.base64-encode

这里表示在读取文件时,使用 convert.base64-encode 过滤器。

convert.base64-encode 的作用是:

把读取到的文件内容转换成 Base64 编码

比如原本文件内容是:

<?php
$flag = "flag{test}";
?>

经过 Base64 编码后,页面返回的就是一串类似这样的内容:

PD9waHAKJGZsYWcgPSAiZmxhZ3t0ZXN0fSI7Cj8+

拿到这串 Base64 后,再解码,就可以看到原来的 PHP 源码。


  1. resource=flag.php
resource=flag.php

resource 是必选参数。

它表示要读取和处理的目标文件。

这里的意思是:

读取 flag.php 这个文件

所以整体 payload 的意思就是:

读取 flag.php,并把它的内容进行 Base64 编码后返回

为什么要加 Base64 编码

因为如果直接包含:

?file=flag.php

PHP 会执行 flag.php 里的代码,我们不一定能看到源码。

但是使用:

?file=php://filter/read=convert.base64-encode/resource=flag.php

PHP 读取到的是经过 Base64 编码后的内容。

编码后的内容已经不是 PHP 代码格式了,所以不会被正常执行,而是会作为普通文本显示出来。

然后我们再对页面返回的 Base64 内容进行解码,就能拿到 flag.php 的源码。


关于 readwrite 和过滤器

read 表示读取文件时使用过滤器。

write 表示写入文件时使用过滤器。

在读取源码的场景下,一般使用:

read=convert.base64-encode

如果不写 readwrite,PHP 有时会根据场景自动选择合适的过滤方向。

但是为了清晰和稳定,CTF 中通常写完整:

php://filter/read=convert.base64-encode/resource=flag.php

过滤器补充

convert.base64-encode 是一种转换过滤器。

常见过滤器大致可以分为:

  1. 字符串过滤器
  2. 转换过滤器
  3. 压缩过滤器
  4. 加密过滤器

php://filter 中可以使用一个或多个过滤器。

多个过滤器之间可以用 | 连接。

例如:

php://filter/read=过滤器1|过滤器2/resource=目标文件

在 CTF 文件包含题中,灵活使用过滤器经常是解题关键。


最终流程总结

  1. URL 中出现 ?file=flag.php,说明可能通过参数控制文件名。
  2. 猜测后端可能存在文件包含逻辑。
  3. 直接包含 flag.php 时,PHP 代码会被执行,源码不会直接显示。
  4. 使用 php://filter 伪协议读取文件。
  5. 使用 convert.base64-encode 把文件源码转成 Base64 文本。
  6. 页面返回 Base64 内容。
  7. 对 Base64 内容解码,得到 flag.php 源码。
  8. 从源码中找到 flag。

一句话理解

php://filter 的作用就是:

不让 PHP 直接执行目标文件,而是先把目标文件内容转换成 Base64 文本显示出来,从而读取 PHP 源码。


这个 payload 里为什么用 /

?file=php://filter/read=convert.base64-encode/resource=flag.php

原因是:这是 php://filter 伪协议规定的写法,/ 在这里用来分隔不同的参数段。

它不是随便选的符号。

URL修改后得出结果

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

得出结果,复制去提交即可

4、HCTF 2018 WarmUp(白名单校验绕过 + 文件包含漏洞 + 路径穿越)

题目

做法

启动靶机,点进去

没啥信息,点开F12康康源码
发现有被注释的 在URL后面加进去被注释的东西,访问一下

代码分析

访问 source.php 后,可以看到页面直接展示了 PHP 源码:

<?php
    highlight_file(__FILE__);

这里用到了:

  • highlight_file():PHP 内置函数,用于高亮显示指定文件的源码。
  • __FILE__:PHP 魔术常量,表示当前文件的完整路径。

所以这行代码的作用是:

把当前 PHP 文件的源码高亮显示在页面上。


  1. 定义类和检查函数
class emmm
{
    public static function checkFile(&$page)
    {

这里定义了一个类 emmm,并在类里面定义了一个静态方法 checkFile()

解释:

  • class emmm:定义一个名为 emmm 的类。
  • public:表示该方法可以被外部访问。
  • static:表示这是一个静态方法。静态方法不需要先创建对象,可以直接通过类名调用。

普通方法:需要先 new 一个对象,再用 -> 调用

静态方法:不需要 new 对象,直接用 类名::方法名() 调用

例如:

emmm::checkFile("hint.php");

意思是:调用 emmm 类里的 checkFile() 方法,用来检查 "hint.php" 是否合法。

  • checkFile():用于检查传入的文件名是否合法。
  • &$page:表示引用传参。

普通传参时,函数里拿到的是变量的复制品,函数内部修改它,不会影响外面的原变量。

引用传参时,函数里拿到的是原变量本身的别名,函数内部如果修改 $page,外面的原变量也会被修改。

不过这道题里,函数内部并没有直接修改 $page,而是使用了新的变量 $_page,所以这里的 &$page 对实际逻辑影响不大。


  1. 白名单设置
$whitelist = ["source"=>"source.php","hint"=>"hint.php"];

这里定义了一个白名单数组,只允许访问两个文件:

source.php
hint.php

注意:虽然数组的键是 sourcehint,但后面用的是 in_array(),检查的是数组里的,也就是:

source.php
hint.php

  1. 检查参数是否存在且为字符串
if (! isset($page) || !is_string($page)) {
    echo "you can't see it";
    return false;
}

这里做了基础检查:

  • isset($page):判断$page是否存在,并且不是 NULL
  • is_string($page):判断 $page 是否为字符串。

如果 $page 不存在,或者不是字符串,就输出:

you can't see it

然后返回 false,表示检查失败。


  1. 第一次白名单检查
if (in_array($page, $whitelist)) {
    return true;
}

这里直接检查 $page 是否在白名单中。

也就是说,如果传入的是:

source.php

或者:

hint.php

就会返回 true,允许包含该文件。

例如:

?file=hint.php

这时 $_REQUEST['file'] 的值就是:

hint.php

符合白名单,所以可以通过检查。


  1. 截取 ? 前面的内容再检查
$_page = mb_substr(
    $page,
    0,
    mb_strpos($page . '?', '?')
);
if (in_array($_page, $whitelist)) {
    return true;
}

先看这一部分:

mb_strpos($page . '?', '?')

. 是字符串拼接符

在 PHP 里,. 表示拼接字符串。

例如:

$page = "source.php";

echo $page . "?";

结果是:

source.php?

所以:

$page . '?'

意思是:

在 $page 后面再拼接一个 ?

mb_strpos() 是找位置

mb_strpos($page . '?', '?')

意思是:

$page . '?' 这个字符串里,查找第一个 ? 出现的位置。

位置是从 0 开始数的。

比如:

source.php?aaa=bbb

每个字符的位置大概是:

s o u r c e . p h p ? a a a = b b b
0 1 2 3 4 5 6 7 8 9 10

所以第一个 ? 的位置是:

10

也就是说:

mb_strpos("source.php?aaa=bbb?", "?")

结果是:

10

注意这里虽然代码里又拼了一个 ?,但字符串本来已经有 ?,所以找到的是第一个 ?,也就是原本那个。


mb_substr($page, 0, 位置)

再看外层:

mb_substr(
    $page,    
    0,    
    mb_strpos($page . '?', '?')
);

mb_substr() 是截取字符串。

格式可以理解成:

mb_substr(要截取的字符串, 从哪里开始, 截取多长)

所以:

mb_substr($page, 0, 10)

意思是:

$page 的第 0 个位置开始,截取 10 个字符。

如果:

$page = "source.php?aaa=bbb";

那么:

mb_substr($page, 0, 10)

得到:

source.php

因为 source.php 正好是 10 个字符。

这里的逻辑是:

  1. 找到 $page 中第一个 ? 出现的位置。
  2. 截取 ? 前面的内容。
  3. 再判断截取后的内容是否在白名单中。

例如:

source.php?aaa=bbb

截取 ? 前面的内容后,得到:

source.php

source.php 在白名单中,所以检查通过。

这里的:

$page . '?'

是为了防止 $page 中没有 ? 时,mb_strpos() 找不到位置。

如果原字符串里没有 ?,拼接一个 ? 后,就一定能找到 ?


  1. URL 解码后再次检查
$_page = urldecode($page);
$_page = mb_substr(
    $_page,
    0,
    mb_strpos($_page . '?', '?')
);
if (in_array($_page, $whitelist)) {
    return true;
}

这里会先对 $page 进行 URL 解码。

例如:

%3F

解码后是:

?

所以如果传入:

source.php%3Faaa=bbb

经过 urldecode() 解码后变成:

source.php?aaa=bbb

然后再截取 ? 前面的内容,得到:

source.php

如果截取后的结果在白名单中,也会通过检查。


  1. 检查失败
echo "you can't see it";
return false;

如果前面的所有检查都没有通过,就会输出:

you can't see it

然后返回 false,不允许包含文件。


  1. 主程序逻辑
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']

$_REQUEST['file']

表示接收用户传入的 file 参数。

$_REQUEST 可以接收 GET、POST、Cookie 中的参数。

例如访问:

?file=hint.php

那么:

$_REQUEST['file']

拿到的值就是:

hint.php

判断条件

! empty($_REQUEST['file'])

表示 file 参数不能为空。

is_string($_REQUEST['file'])

表示 file 参数必须是字符串。

emmm::checkFile($_REQUEST['file'])

表示调用 checkFile() 方法,检查传入的文件名是否符合白名单规则。

只有这三个条件都满足,才会执行文件包含。


  1. 文件包含点
include $_REQUEST['file'];

这里是核心危险点。

include 会把指定文件包含进当前 PHP 脚本中。

如果被包含的文件是 PHP 代码,那么 PHP 会执行它。

如果被包含的文件是 HTML、图片报错信息、纯文本等内容,可能会直接显示在页面中。

这类代码的风险在于:

如果用户可以控制 include 后面的文件名,并且校验不严格,就可能造成文件包含漏洞。


  1. 初步利用思路

代码中白名单允许访问:

source.php
hint.php

所以可以先访问:

?file=hint.php

因为 hint.php 在白名单中,可以通过检查。

也就是说:

$_REQUEST['file'] = "hint.php";

然后:

emmm::checkFile("hint.php")

返回 true

接着执行:

include "hint.php";

页面就会包含并显示 hint.php 的内容。

这里用了,即通过GET方式传入参数 file=hint.php

为什么可以?

因为这道题代码用的是:

$_REQUEST['file']

$_REQUEST 可以接收 GETPOSTCOOKIE 里的参数。

(直接把根目录下的source.php换成hint.php也行,e.g.http://4751184b-cd59-4060-ad37-b25a06768ecc.node5.buuoj.cn:81/hint.php


总结

这段代码的核心逻辑是:

  1. 页面通过 highlight_file(__FILE__) 显示当前源码。
  2. 程序从 $_REQUEST['file'] 接收用户传入的文件名。
  3. checkFile() 会检查文件名是否符合白名单。
  4. 白名单中只允许 source.phphint.php
  5. 如果检查通过,就执行:
include $_REQUEST['file'];
  1. 因为 hint.php 在白名单中,所以可以先构造:
?file=hint.php
  1. 通过包含 hint.php 查看提示,继续寻找 flag 位置。

得出,我们要的flag就在ffffllllaaaagggg这里

至此,我们编写payload

source.php?file=hint.php?/../../../../ffffllllaaaagggg

or

source.php?file=source.php?/../../../../ffffllllaaaagggg

解释:

  1. 为什么要在 hint.php 后面加 ?

构造的 payload 类似:

?file=hint.php?/../../../../ffffllllaaaagggg

传入 file 参数的值是:

hint.php?/../../../../ffffllllaaaagggg

代码中会先截取 ? 前面的内容:

$_page = mb_substr(
    $page,
    0,
    mb_strpos($page . '?', '?')
);

所以:

hint.php?/../../../../ffffllllaaaagggg

截取 ? 前面的部分后,得到:

hint.php

hint.php 在白名单中,所以检查通过。

因此,? 的作用是:

让白名单检查时只看到 hint.php,从而通过校验。


  1. include 实际包含的是什么?

虽然白名单检查时只检查了 hint.php,但是后面真正执行的是:

include $_REQUEST['file'];

也就是说,include 接收到的仍然是完整内容:

include "hint.php?/../../../../ffffllllaaaagggg";

这里不要理解成:

include "hint.php";

这不严谨。

更准确的理解是:

白名单检查和 include 处理的是同一个字符串,但是检查函数只看 ? 前面的部分,而 include 会尝试按照完整路径去解析。


  1. 为什么 ?/../ 可以绕过?

payload:

hint.php?/../../../../ffffllllaaaagggg

可以拆成:

hint.php? / .. / .. / .. / .. / ffffllllaaaagggg

这里的 hint.php? 可以理解成一个“假的路径段”。

后面的:

..

表示返回上一级目录。

所以路径大致会被规范化成类似:

../../../../ffffllllaaaagggg

也就是通过 ../ 不断向上级目录跳转,最后去包含目标文件:

ffffllllaaaagggg

这就是路径穿越。


  1. 为什么 ? 后面最好接 /

推荐写法:

?file=hint.php?/../../../../ffffllllaaaagggg

而不是:

?file=hint.php?../../../../ffffllllaaaagggg

原因是:

hint.php?/../../../../ffffllllaaaagggg

路径结构更清楚:

hint.php? / .. / .. / .. / .. / ffffllllaaaagggg

也就是:

  • hint.php?:可以理解成一个路径段
  • /:路径分隔符
  • ..:表示返回上一级目录
  • hint.php?/../:可以理解成先出现一个 hint.php? 路径段,再通过 .. 返回上一级,从而把前面的 hint.php? 抵消掉
  • 后面的多个 ../:继续向上跳转目录,最终寻找目标文件
  • /的解释:

开头的 / :从根目录开始,绝对路径
中间的 / :路径分隔符
结尾的 / :表示这是目录
没有开头 / :从当前目录开始,相对路径

可能会说的有点懵,具体来说就是,自己类比一下:

/www/html/..   -> /www
/www/html/../  -> /www/

如果写成:

hint.php?../../../../ffffllllaaaagggg

那么 ? 后面的 .. 不一定会被清晰地当成单独的路径段,解析效果可能不稳定。

所以在这题里,更推荐写:

?file=hint.php?/../../../../ffffllllaaaagggg

简单记:

? 用来骗过白名单检查,../ 用来进行目录穿越。


  1. 为什么不是直接访问 ffffllllaaaagggg

如果直接传:

?file=ffffllllaaaagggg

检查函数会判断:

ffffllllaaaagggg

是否在白名单中。

但白名单只有:

source.php
hint.php

所以会失败。

因此需要构造:

?file=hint.php?/../../../../ffffllllaaaagggg

让检查函数看到的是:

hint.php

但真正包含时,又能通过路径穿越访问到:

ffffllllaaaagggg

  1. 为什么不直接写 hint.php?/ffffllllaaaagggg

看起来:

?file=hint.php?/ffffllllaaaagggg

好像是直接从根目录访问:

/ffffllllaaaagggg

但实际上不是。

因为完整字符串是:

hint.php?/ffffllllaaaagggg

它并不是以 / 开头的绝对路径,而是以:

hint.php?

开头的相对路径。

也就是说,它更像是在找:

hint.php? 这个目录下面的 ffffllllaaaagggg

但是正常情况下并不存在一个叫 hint.php? 的目录,所以可能找不到目标文件。

而:

?file=hint.php?/../../../../ffffllllaaaagggg

里面的第一个:

/..

可以把前面的 hint.php? 这个假路径段抵消掉,然后继续向上跳目录,最终找到目标文件。


  1. ../ 的作用(跟上面有点重合,主要再补充一下)

../ 表示返回上一级目录。

假设当前文件在:(/var/www/html:常见的网站根目录,很多 Apache 环境默认使用它)

/var/www/html/source.php

那么:

../

表示从:

/var/www/html/

返回到:

/var/www/

再来一个:

../../

就是返回到:

/var/

继续:

../../../

就是返回到:

/

所以:

../../../../ffffllllaaaagggg

大致意思是:

不断向上返回目录,最后访问 ffffllllaaaagggg 文件

在 Linux 中,根目录 / 已经是最顶层目录了,再继续 ../ 一般还是停留在 /,所以有时多写几个 ../ 也能成功。

但是实际做题时,../ 的数量需要根据题目环境尝试。

可能会看/在不同的地方会有点懵,主要是这样的,可以结合上面的知识点深入理解一下:

.. 的核心含义是返回上一级目录。

/ 只是路径分隔符,但它出现在不同位置时含义会不同:

  • .. 返回上一级目录
  • ../ 返回上一级目录,后面的 / 表示还可以继续接路径
  • /.. 前面的 / 是路径分隔符,.. 表示返回上一级目录
  • /../ 前面的 / 是路径分隔符,.. 表示返回上一级目录,后面的 / 表示还可以继续接路径

在目录穿越 payload 中,真正起作用的是 ../ 主要负责分隔路径。


  1. 最终 payload

最终构造:

?file=hint.php?/../../../../ffffllllaaaagggg

完整访问形式类似:

/source.php?file=hint.php?/../../../../ffffllllaaaagggg

含义是:

  1. file 参数传入 hint.php?/../../../../ffffllllaaaagggg
  2. 检查函数截取 ? 前面的 hint.php
  3. hint.php 在白名单中,检查通过
  4. include 使用完整路径进行包含
  5. 通过 ../ 进行路径穿越
  6. 最终包含并读取 ffffllllaaaagggg

  1. 一句话总结

这题的关键不是 hint.php 自己再次 include 了什么,而是:

检查函数只检查 ? 前面的白名单文件名,但真正 include 时使用完整字符串,导致可以在白名单文件名后拼接 ?/../../../../目标文件,通过路径穿越包含目标文件。

得出flag

复制,回去提交

感谢阅读!如果你觉得这篇文章对你有帮助,欢迎扫码赞赏支持,你的鼓励是我持续创作的动力 ❤️

本文为原创内容,转载请注明出处。
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇