首页
社区
课程
招聘
[原创]跟着crownless学Web之(5)Hack.lu 2018 Babyphp赛题讲解
发表于: 2019-1-29 16:45 9821

[原创]跟着crownless学Web之(5)Hack.lu 2018 Babyphp赛题讲解

2019-1-29 16:45
9821

本文备份于我的博客
大家好,我是Web安全板块的新版主crownless。最近我将以CTF赛题讲解的形式介绍一系列的Web基础知识,讲解的顺序将是循序渐进,因此不需要读者有任何基础知识。希望能吸引更多人关注看雪的Web安全板块,为板块增加人气和活力。
如果你还没有阅读第一篇教程第二篇教程第三篇教程第四篇教程,建议你先阅读之后再阅读本文。
在这篇文章中,你将学到:

  1. php require_once函数
  2. php require函数
  3. php include函数
  4. php error_reporting函数
  5. php highlight_file函数
  6. URL编码
  7. php file_get_contents函数进阶——data://text/plain;base64,
  8. php intval函数
  9. php ===(全等)运算符
  10. php ||(逻辑或)运算符
  11. php strlen函数
  12. php preg_match函数
  13. 正则表达式入门
  14. php is_numeric函数
  15. php list函数
  16. php substr函数
  17. php sha1函数
  18. php foreach语法结构
  19. php可变变量
  20. php assert_options函数
  21. php eval函数
  22. php assert函数

让我们首先打开赛题链接:http://111.231.88.117/babyphp/
你会看到如下php代码:

<?php
require_once('flag.php');
error_reporting(0);

if(!isset($_GET['msg'])){
    highlight_file(__FILE__);
    die();
}

$msg = $_GET['msg'];
if(file_get_contents($msg)!=="Hello Challenge!"){
    die('Wow so rude!!!!1');
}

echo "Hello Hacker! Have a look around.\n";

$k1=$_GET['key1'];
$k2=$_GET['key2'];

$cc = 1337;$bb = 42;

if(intval($k1) !== $cc || $k1 === $cc){
    die("lol no\n");
}

if(strlen($k2) == $bb){
    if(preg_match('/^\d+$/', $k2) && !is_numeric($k2)){
        if($k2 == $cc){
            $cc = $_GET['cc'];
        }
    }
}

list($k1,$k2) = [$k2, $k1];

if(substr($cc, $bb) === sha1($cc)){
    foreach ($_GET as $lel => $hack){
        $$lel = $hack;
    }
}

$b="2";$a="b";
if($$a !== $k1){
    die("lel no\n");
}

// plz die now
assert_options(ASSERT_ACTIVE, 1);
assert_options(ASSERT_BAIL, 1);
eval("$bb=$cc");
assert("$bb=$cc");
echo "Good Job ;)\n";
//TODO
//echo $flag;  

?>

源码量还是不小的,让我们耐心地分析。
首先,程序执行了require_once('flag.php');
require_once函数的定义是“require_once语句和require语句完全相同,唯一区别是PHP会检查该文件是否已经被包含过,如果是则不会再次包含。”。
require语句的作用是“require和include几乎完全一样,除了处理失败的方式不同之外。require在出错时产生E_COMPILE_ERROR级别的错误。换句话说将导致脚本中止而include只产生警告(E_WARNING),脚本会继续运行。”。
include语句“包含并运行指定文件”。
那么什么是“包含并运行”呢?你可以理解为把要“包含”的文件原封不动地替换到require_oncerequireinclude函数所在的位置,并执行其中的php语句。以require_once('flag.php');为例,flag.php这个文件中可能存在$flag='xxx';,也就是我们想夺取的flag的内容。require_once函数的作用是在php后端执行到require_once时立即将flag.php的内容替换require_once('flag.php');,并在我们当前审计的php后端中执行flag.php中的所有内容。在php开发中,适当使用require_oncerequireinclude可以提高代码复用程度,减少代码冗余。而在Web类型的CTF比赛中,将flag单独写在flag.php中并通过require_oncerequireinclude包含进来,可以避免在主程序中暴露flag的内容。
接着,程序执行了error_reporting(0);error_reporting可以“设置应该报告何种PHP错误”。而error_reporting(0);可以关闭所有PHP错误报告。这句话并不妨碍我们做题。
接着运行了如下代码段:

if(!isset($_GET['msg'])){
    highlight_file(__FILE__);
    die();
}

highlight_file可以“语法高亮一个文件”,也就是将参数所指的文件语法高亮后发送到前端。这里用到的的参数是__FILE__,这是一个魔术常量,代表了本文件的完整路径和文件名。我们以前已经学过isset函数了。所以我们要做的是在URL中传入msg参数,这样才会不执行到die();
继续往下看,运行了如下代码段:

$msg = $_GET['msg'];
if(file_get_contents($msg)!=="Hello Challenge!"){
    die('Wow so rude!!!!1');
}

echo "Hello Hacker! Have a look around.\n";

如果你阅读过第四篇教程,应该知道需要给msg参数传入data://text/plain,Hello Challenge!,才能避免执行到die('Wow so rude!!!!1');。但是当你在地址栏输入http://111.231.88.117/babyphp/?msg=data://text/plain,Hello Challenge!并按回车时,地址栏中的地址居然变成了http://111.231.88.117/babyphp/?msg=data://text/plain,Hello%20Challenge!,也就是Hello和Challenge之间的空格变成了%20,这是为什么呢?其实,这是浏览器把空格符号进行了URL编码(URL Encode)。如果不这么做,将违反HTTP协议,导致后端返回400 Bad Request错误,如下图所示:

使用URL编码还有一个好处。以第三篇教程为例,如果你想给a参数传入123&456=,而不进行URL编码的话,后端接收到的HTTP请求将是

GET /dmsj/level0/?a=123&456= HTTP/1.1

就会理解为传入了两个参数:a456,这明显与我们原来的意图不符。所以,浏览器会自动对发送的表单的数据进行URL编码。
好了,现在我们访问http://111.231.88.117/babyphp/?msg=data://text/plain,Hello%20Challenge!,将会显示“Hello Hacker! Have a look around. lol no”,意味着echo "Hello Hacker! Have a look around.\n";已经成功执行。不过不要急,这里我还要教你一种让file_get_contents函数返回"Hello Challenge!"的方法,那就是使用data://text/plain;base64,。base64是另一种比较常用的编码,这个网站可以转换明文和base64编码。我们可以将Hello Challenge!输入进去,将会转化成SGVsbG8gQ2hhbGxlbmdlIQ==,我们接着将其与http://111.231.88.117/babyphp/?msg=data://text/plain;base64,拼接,得到http://111.231.88.117/babyphp/?msg=data://text/plain;base64,SGVsbG8gQ2hhbGxlbmdlIQ==。当后端执行到file_get_contents("data://text/plain;base64,SGVsbG8gQ2hhbGxlbmdlIQ==")时,将会首先从data://text/plain;base64,判定这是base64编码过的数据,然后就会把SGVsbG8gQ2hhbGxlbmdlIQ==解码为Hello Challenge!,从而返回Hello Challenge!
让我们继续往下看:

$k1=$_GET['key1'];
$k2=$_GET['key2'];

$cc = 1337;$bb = 42;

if(intval($k1) !== $cc || $k1 === $cc){
    die("lol no\n");
}

程序接着将key1参数赋值给k1,将key2参数赋值给k2。接着设置cc为1337,bb为42。接下来,if语句中有die("lol no\n");,所以我们的目标是让if返回FALSE。而根据||的运算规则,必须让intval($k1) !== $cc$k1 === $cc都返回FALSE才行。intval可以获取变量k1的整数值。我们的目标是让!==这个比较返回FALSE,那么就要做到intval($k1) === $cc,而cc为1337,那么我们只要传入key1=1337即可。由于||运算符的特性,当intval($k1) !== $cc返回FALSE时还会执行到$k1 === $cc===被称为全等运算符,效果是当$k1等于$cc并且它们的类型也相同时,才会返回TRUE。那么,因为$k1是字符串类型,而$cc是整数类型,所以$k1 === $cc会返回FALSE,正是我们想要的。所以,我们当前的payload是:

 

http://111.231.88.117/babyphp/?msg=data://text/plain;base64,SGVsbG8gQ2hhbGxlbmdlIQ==&key1=1337

 

访问以上网址,显示“Hello Hacker! Have a look around. lel no”,注意这里显示的是“lel no”而不是“lol no”,说明我们顺利通过了这一关。

 

继续往下看:

if(strlen($k2) == $bb){
    if(preg_match('/^\d+$/', $k2) && !is_numeric($k2)){
        if($k2 == $cc){
            $cc = $_GET['cc'];
        }
    }
}

strlen可以计算字符串的长度。从这段代码中我们得知k2的长度必须为42,且is_numeric($k2)必须返回FALSEpreg_match('/^\d+$/', $k2)必须返回TRUE$k2 == $cc必须返回TRUE,同时满足这几个条件,程序才会执行$cc = $_GET['cc'];
很显然,我们必须控制cc变量,所以我们要尝试满足以上提到的所有条件。preg_match函数可以“执行匹配正则表达式”。可能你没有学过正则表达式,不要紧,我们这里只学一小点。
首先,preg_match的第一个参数是“要搜索的模式”。什么是“模式”呢?你可以理解为其“代表了一种规则”。模式需要由分隔符(delimiter)闭合包裹。那么这里的分隔符显然是/,因为最开始和最后两个/包裹了“模式”。preg_match的第二个参数是“源字符串”。
那么什么是“匹配正则表达式”呢?意思就是“源字符串符合模式所指的规则”。
关于正则表达式,这篇文章是很好的入门材料,有兴趣的读者可以参考。
回到我们的赛题。那么^\d+$代表什么规则呢?首先,^匹配源字符串的开始位置。\d匹配一个数字字符。+匹配前面的子表达式一次或多次。$匹配输入字符串的结束位置。合在一起,规则可以表达为:源字符串的内容必须为一个或多个数字。
为了测试一个源字符串是否符合模式,我们可以访问这个网站。在大输入框中输入源字符串,比如1111111111,将^\d+$复制到“正则表达式”输入框中,点击“测试匹配”,结果居然发现“没有匹配”。
出了什么问题呢?如果你足够细心,应该能发现符号和$符号是不一样的!$匹配输入字符串的结束位置,是一个“元字符”,而仅仅是一个普通的字符。所以我们理解错了规则。正确的规则是“源字符串必须以一个或多个数字作为开始,然后跟着符号”。让我们在大输入框中输入1111111111$,就能成功找到一处匹配。
现在我们已经能让preg_match('/^\d+$/', $k2)返回TRUE,但必须还要满足其他条件。那么is_numeric($k2)是否会返回FALSE呢?答案是肯定的!is_numeric检测变量是否为数字或数字字符串,因为符号不是数字,所以is_numeric($k2)会返回FALSE
未完待续。


[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

最后于 2019-2-1 16:50 被crownless编辑 ,原因:
收藏
免费 1
支持
分享
打赏 + 2.00雪花
打赏次数 2 雪花 + 2.00
 
赞赏  一半人生   +1.00 2019/01/31
赞赏  CCkicker   +1.00 2019/01/31 感谢分享~
最新回复 (12)
雪    币: 403
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
给大佬点赞
2019-1-31 13:08
0
雪    币: 5883
活跃值: (12394)
能力值: ( LV12,RANK:312 )
在线值:
发帖
回帖
粉丝
3
1、base64利用工具计算出来的值:SGVsbG8lMjBDaGFsbGVuZ2UlMjE= 也可以..... 
      计算Base64为什么会不一样呢? 都是可以正常执行的

2、以这种格式获取的都是 $_GET['xxx'] 都是字符串形式?
2019-1-31 16:27
0
雪    币: 2282
活跃值: (426)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
4
1、SGVsbG8lMjBDaGFsbGVuZ2UlMjE=是Hello%20Challenge%21的base64编码,并不与Hello Challenge!相等,也无法执行到echo "Hello Hacker! Have a look around.\n"; 你可能弄错了
2、对。
最后于 2019-1-31 17:45 被crownless编辑 ,原因:
2019-1-31 17:45
0
雪    币: 5883
活跃值: (12394)
能力值: ( LV12,RANK:312 )
在线值:
发帖
回帖
粉丝
5
 对应该事在线编码弄错了- -
最后于 2019-1-31 19:46 被一半人生编辑 ,原因:
2019-1-31 19:45
0
雪    币: 5
活跃值: (168)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
感谢版主~
2019-3-3 16:03
0
雪    币: 214
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
厉害呀
2019-3-4 17:02
0
雪    币: 1095
活跃值: (165)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
版主什么时候再更新,迫不及待了
2019-3-6 19:42
0
雪    币: 2282
活跃值: (426)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
9
孑狱原 版主什么时候再更新,迫不及待了
最近工作比较忙,我争取周末更新!
2019-3-7 21:06
0
雪    币: 5883
活跃值: (12394)
能力值: ( LV12,RANK:312 )
在线值:
发帖
回帖
粉丝
10
等着更新 二进制转学WEB
2019-3-28 08:07
0
雪    币: 17
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
11
为什么我一直网页都是404
2019-3-31 20:53
0
雪    币: 5883
活跃值: (12394)
能力值: ( LV12,RANK:312 )
在线值:
发帖
回帖
粉丝
12
这系列的课程后续还迭代吗lz?
2020-4-1 09:02
0
雪    币: 2282
活跃值: (426)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
13
一半人生 这系列的课程后续还迭代吗lz?
因为作者有些别的事要干,而且缺乏托管赛题的服务器,所以暂时停止了。
2020-5-22 09:04
0
游客
登录 | 注册 方可回帖
返回
//