24-25下半学年第二次线上赛 | Writeup

莫斯档案馆 每一层有一个pwd.png和一个压缩包,pwd.png是一个彩色条纹和圆点的非常小的图像,也就是摩斯密码,套了很多很多层,写脚本去识别摩斯密码,并递归解压。 from PIL import Image import re def getMorse(image): """ 从图像中提取莫尔斯电码 假定背景颜色是固定的,莫尔斯电码具有不同的颜色。 莫尔斯电码可以是任何颜色,只要它与左上像素的颜色不同。 >>> getMorse('pwd.png') ['----.'] """ im = Image.open(image, 'r') chars = [] background = im.getdata()[0] for i, v in enumerate(list(im.getdata())): if v == background: chars.append(" ") else: chars.append("*") output = "".join(chars) # 清理输出,去除前后的空白 # 然后将每组3个星号转换为短横线 # 将星号转换为实际的点 # 将字母之间的空格(即>1个背景像素)转换为分隔符 # 删除空白 # 返回字母的列表 output = re.sub(r'^\s*', '', output) output = re.sub(r'\s*$', '', output) output = re.sub(r'\*{3}', '-', output) output = re.sub(r'\*', '.', output) output = re.sub(r'\s{2,}', ' | ', output) output = re.sub(r'\s', '', output) output = output.split('|') return output def getPassword(morse): """ 解码莫尔斯电码 将莫尔斯电码转换回文本。 以字母列表为输入,返回转换后的文本。 注意,挑战使用小写字母。 >>> getPassword(['----.']) '9' """ MORSE_CODE_DICT = { '.-': '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 item in morse: return "".join([MORSE_CODE_DICT.get(item) for item in morse]).lower() def main(): """ 自动启动 用于自动化。 自动调用方法并使用'pwd.png'作为输入图像。 """ print(getPassword(getMorse("pwd.png"))) if __name__ == "__main__": main() 并使用sh脚本去循环执行 ...

March 31, 2025 · 9 min · Red

[技术杂谈]安全的个人邮箱服务搭建

⚠️本篇属于过程回忆,我的搭建过程并不完全按照文章路线来,期间解决dovect和certbot导致我的整个搭建过程比较混乱,本篇文章是事后总结出的一条比较可靠的搭建路线,如果照着文章搭建有概率碰到奇怪的小问题,毕竟我也没有验证^_^ 一个合格的hacker一定有一个自己的邮箱地址和服务 我的搭建环境为AWS送的一年EC2,系统Debian(一年后到期再说吧= = 域名为porkbun买的.space,并套上cf监控域名访问情况(如果有小可爱在渗透就可以看到) 采购阶段 首先是域名,这个没啥好说的,想在哪里买域名都可以 不过有个比较想骂人的地方是在porkbun买的时候碰到身边七年老用户都没见过的逆天支付bug 成功付款后域名并未到账= = 当时急得不行,发邮件反馈等了一天,最后官方给我平台自己的等值货币 但是…… 处理很人性化,后来朋友说应该是striple接口的问题而不是porkbun的问题,所以大家要想用porkbun还是可以放心用,要是碰到我这样的情况也别慌,发邮件反馈,一天内就会回复(应该不是每个人都有我这样的bug触发圣体…… 然后是vps,EC2领取教程网上一搜一把,我就不多介绍了 搭建阶段 1 - 安装postfix和dovecot并进行基本配置 AWS选择debian系统,进去后先安装postfix和devocot。前者用于发件,后者用于收件 sudo apt update && sudo apt upgrade -y sudo apt install postfix dovecot-core dovecot-imapd mailutils -y 这里我没有安装pop3,如果需要pop3可以再加一句apt install dovecot-pop3d -y 顺便简单介绍一下pop3和imap的区别 POP3: POP3通过将邮件从服务器下载到本地设备(如电脑或手机)来工作。下载后,邮件通常会从服务器上删除(除非手动设置保留副本)。它更像是一种“取走邮件”的方式,适合离线阅读。 IMAP: IMAP允许你在服务器上直接管理和查看邮件,而无需将它们下载到本地设备。本地设备只是显示服务器上的邮件内容,所有操作(如删除、移动文件夹)都会同步到服务器。 接着配置postfix vim /etc/postfix/main.cf myhostname = yourdomainname.com mydomain = yourdomainname.com myorigin = /etc/mailname mydestination = $myhostname, localhost.$mydomain, localhost inet_interfaces = all inet_protocols = all home_mailbox = Maildir/ 然后配置dovecot,首先配置主文件 ...

March 28, 2025 · 2 min · Red

[靶场笔记]第二十三章

PHP中的特殊md5值 之前碰到过很多md5绕过的小技巧,但是很少提到md5本身的特殊值(印象中只有一个万能密码)。在PHP中,INF和NAN分别表示最大值和最小值,此时如果有类似下面这样的判断条件 if (($dsb !== $this->ctf) && ($this->ctf !== $dsb)) { if (md5($dsb) === md5($this->ctf)) { echo file_get_contents("/flag.txt"); } } 就可以利用INF的特性绕过。因为在PHP中,像9e999999这样庞大的数字时没法超过INF的,所以PHP就会干脆把它直接当成INF,所以INF和9e999999的md5值是相等的。需要注意的是,INF可以是字符串也可以是一种数据,也就是说不用加单引号,因为不管是字符串还是无穷大在md5时都会被当作无穷大(这点就很有意思,可能存在其他的绕过漏洞)。但9e999999一定不能加单引号,否则会被当作字符串 命名空间 之前比较少提到这个,因为国内ctf的php pop题大都是在一个文件里实现的。不过今天偶然遇到了跨文件的pop链构造,就顺便记录一下 想下面这样,就是一个命名空间内的拓展类的定义 namespace Helpers{ use \ArrayIterator; class ArrayHelpers extends ArrayIterator { public $callback; public function current() { $value = parent::current(); $debug = call_user_func($this->callback, $value); return $value; } } } 意思就是定义了一个叫Helpers命名空间,在这个命名空间里可以随意定义各种变量、类的名字,不会和php自带的根空间冲突。比如这个ArrayHelpers就有可能会和php某个自带的函数名冲突,所以才需要再创建一个命名空间来避免这种冲突。 并且ArrayHelpers是ArrayIterator的扩展类,可以看到代码里还use了一下,说明它希望使用ArrayIterator这个命名空间里的一些函数,而不是使用php自带的。比如下面的current 假设这段代码被单独放到一个文件里,那么要引用他就需要先将这个文件include一下,然后再use 这里有一个很重要的细节,就是Helpers\ArrayHelpers和\Helpers\ArrayHelpers本质上是两种完全不同的东西。其实就和绝对/相对路径的道理一样,前者是当前命名空间下的命名空间引用,后者是从根空间开始引用。 如果在构造pop链的时候发现语法正确但无响应,就可以检查一下是不是命名空间的路径错了 java h2数据库 这是java自带的一种嵌入式数据库,之所以叫嵌入式,不仅是因为它可以被当成java的包被import,跟重要的是它能够执行java代码。也就是说,一旦存在sql拼接,像下面这样存在sql注入的话 String query = String.format("Select * from notes where name ='%s' ", name); 就可以通过创建alias调用java代码,从而达到RCE的效果 ...

March 21, 2025 · 1 min · Red

[靶场笔记]第二十三章

PHP中的特殊md5值 之前碰到过很多md5绕过的小技巧,但是很少提到md5本身的特殊值(印象中只有一个万能密码)。在PHP中,INF和NAN分别表示最大值和最小值,此时如果有类似下面这样的判断条件 if (($dsb !== $this->ctf) && ($this->ctf !== $dsb)) { if (md5($dsb) === md5($this->ctf)) { echo file_get_contents("/flag.txt"); } } 就可以利用INF的特性绕过。因为在PHP中,像9e999999这样庞大的数字时没法超过INF的,所以PHP就会干脆把它直接当成INF,所以INF和9e999999的md5值是相等的。需要注意的是,INF可以是字符串也可以是一种数据,也就是说不用加单引号,因为不管是字符串还是无穷大在md5时都会被当作无穷大(这点就很有意思,可能存在其他的绕过漏洞)。但9e999999一定不能加单引号,否则会被当作字符串 命名空间 之前比较少提到这个,因为国内ctf的php pop题大都是在一个文件里实现的。不过今天偶然遇到了跨文件的pop链构造,就顺便记录一下 想下面这样,就是一个命名空间内的拓展类的定义 namespace Helpers{ use \ArrayIterator; class ArrayHelpers extends ArrayIterator { public $callback; public function current() { $value = parent::current(); $debug = call_user_func($this->callback, $value); return $value; } } } 意思就是定义了一个叫Helpers命名空间,在这个命名空间里可以随意定义各种变量、类的名字,不会和php自带的根空间冲突。比如这个ArrayHelpers就有可能会和php某个自带的函数名冲突,所以才需要再创建一个命名空间来避免这种冲突。 并且ArrayHelpers是ArrayIterator的扩展类,可以看到代码里还use了一下,说明它希望使用ArrayIterator这个命名空间里的一些函数,而不是使用php自带的。比如下面的current 假设这段代码被单独放到一个文件里,那么要引用他就需要先将这个文件include一下,然后再use 这里有一个很重要的细节,就是Helpers\ArrayHelpers和\Helpers\ArrayHelpers本质上是两种完全不同的东西。其实就和绝对/相对路径的道理一样,前者是当前命名空间下的命名空间引用,后者是从根空间开始引用。 如果在构造pop链的时候发现语法正确但无响应,就可以检查一下是不是命名空间的路径错了 java h2数据库 这是java自带的一种嵌入式数据库,之所以叫嵌入式,不仅是因为它可以被当成java的包被import,跟重要的是它能够执行java代码。也就是说,一旦存在sql拼接,像下面这样存在sql注入的话 String query = String.format("Select * from notes where name ='%s' ", name); 就可以通过创建alias调用java代码,从而达到RCE的效果 ...

March 21, 2025 · 1 min · Red

2025长城杯半决赛 | Writeup

AWDP CCforum 粗略审计后大概可以知道应该是要先登陆进admin.php,但sql语句全部做了预编译所以没法注入,试了一下通过爆破可以获取到admin.php的账号密码。登陆进去后再看有什么利用点 入口没有什么特别的,可以直接先看到login的逻辑 可以看到把通过验证的用户名写到了session里,接着看reply.php和post.php会发现都有一个敏感词过滤。并且他们调用的$username都是session里的明文用户名 跟进到config.php看这两个函数 可以看到record_banned函数里主要干了三件事 拿用户名base64的结果创建目录 判断是否创建成功,如果创建成功就正常写入被过滤的敏感词。创建失败有两个分支,一个是文件夹创建失败,会把用户名明文拿去和日志信息做拼接;一个是banned日志文件创建失败,直接写入一串信息,没有拼接。 最后将第二点走完的结果用log_action函数写到总日志里(这里又个很容易掉进去的陷阱,就是日志其实是有两份的,一个是总日志access.log,一个是banned日志。我们能作拼接任意写入操作的其实是总日志。) 到这里其实已经可以达到写入任意字符串的目的,但还不能说明什么,因为写入的地址并不能被访问,而且文件名是固定的,所以直接上马可能性不大,得找地方利用这个允许我们随意写东西的地方。 看回刚刚爆破开的admin.php,在登陆验证逻辑过后紧接着这一段 可以看到它把log文件读到变量$action_log,接着$log_lines再以换行作为分隔符将日志作切割。这里我们再看回到config.php,来看一下日志是怎么写的。 可以看到$log_file变量就是以换行为结尾的,并且以逗号为分割。所以我们继续往下看admin.php。可以发现我们写入的东西都会被处理完后放到对应的变量里,而只有$additional_info是我们可以控制的($encoded_user是base64,控制不了)。 接着往下看 这段代码将$encoded_user作为用户名拼接路径,然后再把路径的内容读出来。那么利用思路就清晰了,我们刚才一直在找的任意写入应该就是在这里发挥用处,也就是控制$encoded_user。但这不就和上面说的不能控制矛盾了吗?有什么办法可以通过$additional_info来控制$encoded_user吗? 如果你的思维足够跳跃,应该就能想到用任意文件写入来写入换行,以此在$additional_info来创建一条假日志。这样我们就能控制这条假日志的用户名了。说起来有点抽象,我们来看个具体的例子 这是根据log_action函数生成出来的日志,大概像下面这样 1,abwidab,admin_login,1,\n //log_action($username, 'admin_login', 1); 2,dqbvwwfqiq,register,1,\n //log_action($username, 'register', 1); 3,wqifbqwibc,record_banned,0,Failed to record banned content\n //当record_banned无法创建目录但可以创建文件时log_action($username, 'record_banned', 0, 'Failed to record banned content'); 4,wqifbqwibc,record_banned,0,Failed to create record directory for 这里拼接任意字符串\n //当record_banned无法创建目录但可以创建文件时log_action($username, 'record_banned', 0, 'Failed to create record directory for '.$username); 那么username应该输入什么呢?示例如下 2,dqbvwwfqiq,register,1,\n 4,wqifbqwibc,record_banned,0,Failed to create record directory for \n 11,../flag,record_banned,1,111\n 而我们输入的用户名就是 ...

March 18, 2025 · 1 min · Red