2024 XYCTF比赛 writeup

前言

参加了XYCTF,没写几道题,但还是在此记录一下。

有什么不对的地方欢迎指正。

(吐槽一下,比赛做了几天后就做不动了,一个月时间是真的长。不想做了,不过这篇WP还是定在了比赛结束再发)

Misc

## 1.签到

题目:扫描二维码关注公众号,发送“XYCTF2024,启动启动启动!!!”获取flag

### 解题过程:

没什么好说的

flag:

​XYCTF{WELCOME_TO_XYCTF}

## 2.game

题目:adwa最近迷恋上了一款游戏,他给我们发了这款游戏里的一个解密项目,请你根据这张图片,找出这个游戏的英文名,并用XYCTF{}包括 (每个单词开头要大写,例如XYCTF{Dead Cells})

图片:

### 解题过程:

直接在谷歌上对图片进行搜索,直接找到答案。

flag为:

​XYCTF{Papers Please}

## 3.熊博士

题目:熊大熊二在森林里玩耍的时候捡到了一张小纸条,可能事关森林的安危,但是上面的字他们看不懂,你能帮他们看看这些神秘的字符是什么意思吗?

附件:给了一张图片和一个小纸条txt文件如下:

纸条内容:

CBXGU{ORF_BV_NVR_BLF_CRZL_QQ}

提示:

  • flag头为大写
  • 大写不对要不要试试小写呢

### 解题过程:

由于给了一张图片,以为作者会在图片上做文章,就先对图片进行了一系列分析操作,结果一无所获(图片啥也没有,小丑-->我)

无果后对纸条上的密码进行了凯撒如下:

转换后 —> XWSBP{JMA_WQ_IQM_WGA_XMUG_LL}

发现不符合XYCTF头。但觉得也不像其他密码,尝试栅栏也不对,没有思路了。。。

后来我想 CB和XY都是相邻字母,与凯撒很像。

于是我让B -> Y 的话,发现可以尝试用以下规则进行解码:

A -> Z
B -> Y
...
...
...
Y -> B
Z -> A

成功解出flag:

XYCTF{liu_ye_mei_you_xiao_jj}

#不过这个flag我也是无语了(你可以尝试拼写一下)。

## 4.zzl的护理小课堂

题目:网安学累了吧,zzl说给大家出点护理题放松放松

### 解题过程:

进入网址,如下让我们填选择题:

直接看网页源码,在源码最后看到关键,如下:

一眼看到了主要的代码:

if (score == 100) {
      document.getElementById('scoreDisplay').innerText = "你的分数是: " + score + "/100 杂鱼,怎么才100分啊";
 } else if (score < 100) {
    document.getElementById('scoreDisplay').innerText = "你的分数是: " + score + "/100 noooooob!!";
    } 
    else{
      ...
      ...
      ...
    }

就是说不管我们做选择题是否满分,都没有用。

也就是说,我们应该让score>100;才能继续执行有关flag的代码。

那就先点击提交,bp抓包,然后尝试修改响应包,看是否可行。

如何修改响应包:抓到数据包后右键->Do intercept -> 点击Response to this request,然后放包,直到该请求返回响应包。

响应包如图:

将0修改为1000(大于100的数字都可以)。放包即可。看回显:

没想到直接返回flag了。

flag为:

XYCTF{ZZL_TelI_yOU_83fd07835126}

# Crypto

## 1.Sign1n[签到]

题目:看看密码签到题吧 :D

附件:给了一个txt文件,内容如下:

from Crypto.Util.number import *
from tqdm import *
import gmpy2
flag=b'XYCTF{uuid}'
flag=bytes_to_long(flag)
leak=bin(int(flag))
while 1:
    leak += "0"
    if len(leak) == 514:
        break

def swap_bits(input_str):
    input_list = list(input_str[2:])
    length = len(input_list)

    for i in range(length // 2):
        temp = input_list[i]
        input_list[i] = input_list[length - 1 - i]
        input_list[length - 1 - i] = temp

    return ''.join(input_list)

input_str = leak
result = swap_bits(input_str)
a=result

def custom_add(input_str):
    input_list = list(input_str)
    length = len(input_list)
    
    for i in range(length):
        input_list[i] = str((int(input_list[i]) + i + 1) % 10)

    result = ''.join(input_list)
    return result


input_str = a
result = custom_add(input_str)
b=result
print(b)
#12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567891134567799023445779912234577900124456899023346779001344577801223566780112445788912335667990234457780122355788001244568990133566780013445778902335578800133467889112356679902344567991223557880013455788902335667990234457799023355788001235568990133566789113445679912235577801123457889112456678911245578801233467789112355779912234577990233556780113

### 解题过程:

首先代码审计:

上面代码就是将flag转换成数字,再转成二进制,然后在字符串后面补0凑成长度为514位的字符串,再用swap_bits函数将字符串首尾翻转,最后再用custom_add函数将每一位上的数字X(这里设每一位上的数字为X),让(X+i+1)%10取模,之后输出了模的字符串。

分析完之后就很清晰了,写解码脚本:

from Crypto.Util.number import *


b = 12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567891134567799023445779912234577900124456899023346779001344577801223566780112445788912335667990234457780122355788001244568990133566780013445778902335578800133467889112356679902344567991223557880013455788902335667990234457799023355788001235568990133566789113445679912235577801123457889112456678911245578801233467789112355779912234577990233556780113

input_list = list(str(b))
length = len(str(b))

for i in range(length):
    for x in range(10):
        if (x + i + 1 - int(input_list[i])) % 10 == 0:
            input_list[i] = str(x)
            break
leak = ''.join(input_list)
print(leak)

input_list = list(leak)
length = len(input_list)
for i in range(length // 2):
    temp = input_list[i]
    input_list[i] = input_list[length - 1 - i]
    input_list[length - 1 - i] = temp
leak = ''.join(input_list)

#由于不知道有效长度是多少,所以就把可能的长度都遍历了一遍
for i in range(352, 514):
    list = leak[:i]
        #print(leak)
    flag = int(list, 2)
        #print(flag)
    flag = long_to_bytes(flag)
    print(flag)

运行结果为:

解得flag:

XYCTF{d90e444d-e967-4919-a24b-9972c93fb459}

## 2.happy_to_solve1

题目:so happy

附件:happy_to_solve.py内容如下:

from Crypto.Util.number import *
import sympy
from secrets import flag


def get_happy_prime():
    p = getPrime(512)
    q = sympy.nextprime(p ^ ((1 << 512) - 1))
    return p, q


m = bytes_to_long(flag)
p, q = get_happy_prime()
n = p * q
e = 65537
print(n)
print(pow(m, e, n))
# 24852206647750545040640868093921252282805229864862413863025873203291042799096787789288461426555716785288286492530194901130042940279109598071958012303179823645151637759103558737126271435636657767272703908384802528366090871653024192321398785017073393201385586868836278447340624427705360349350604325533927890879
# 14767985399473111932544176852718061186100743117407141435994374261886396781040934632110608219482140465671269958180849886097491653105939368395716596413352563005027867546585191103214650790884720729601171517615620202183534021987618146862260558624458833387692782722514796407503120297235224234298891794056695442287

### 解题过程:

先看一下代码,意思就是说

m为flag,p是随机生成的一个512位的质数,q是等于p和全1的异或运算,也就是q是p的按位取反~p(如果不清楚为什么,可能是因为你还不知道 "<<" ,“ ^ ”是什么意思,上网一查就知道了,这里不在赘述)

接着往下看,n = p*q ,e = 65537 , c =pow(m, e, n) (这里我们用c来表示加密后的密文)。

了解RSA加密的都知道,接下来应该求什么。如果不知道的话,可以先去网上学习一下。

那么现在求m的话代码如下:

m = pow(c, d, n)  #因为e为加密钥,这里d就是解密钥。(公钥加密 私钥解,私钥加密 公钥解)

那么就要求解密钥d:

path = (p-1)*(q-1)
d = gmpy2.invert(e, path) #不懂这个函数的可以上网查一下

这里就闭环了,想求d就需要知道p和q。

p是一个随机质数,q为p的取反,我们只需要求出其中一个数另一个就可以算出来。但很可惜,我并没有想出什么好的方法,于是尝试暴力破解,成功解出flag,就是过程有些繁琐。(如果有更好的方法希望能告知我)

虽说是暴力,但也不可能直接无脑遍历,毕竟p也是一个10进制有155位的大素数。

所以我用的是趋近地方法,我先随机生成一个p,找到一个p*q接近n的值。尝试多次发现了以11开头的p,结果比较接近n。

之后就每次往后取4~5位进行遍历其余位数就为0,一直往后找趋近于n的值。代码例如:

import sympy

n = 24852206647750545040640868093921252282805229864862413863025873203291042799096787789288461426555716785288286492530194901130042940279109598071958012303179823645151637759103558737126271435636657767272703908384802528366090871653024192321398785017073393201385586868836278447340624427705360349350604325533927890879

for i in range(1000,2000):
  p = "1"+ str(i) + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
  p = int(p)
  q = sympy.nextprime(p ^ ((1 << 512) - 1))
  N = p*q
  #print("p: ", p)
  #print("N: ", N)
  #print("n: ", n)
  if N < n:
    j = i - 1
    #print(j)
    break

之后就把遍历出来的j,取前三位添加到”1后面“,后面的0去掉三个,将j的个位数字作为下次i遍历的范围数。

到最后五位数就可以直接判断N == n,得到最终的p值。

p = 11186104509771514497905845277525414324848282003961401424656189058347435564219816052776565548550435972724614541793612778837623695399649245354880712765072167

(其实应该写一个全自动化的脚本,就不用自己手动输入了,感兴趣的可以自己试一试。我写题写累了,脑子不想再思考了。@.@)

最后运行:

import gmpy2
from Crypto.Util.number import *
import sympy

p = 11186104509771514497905845277525414324848282003961401424656189058347435564219816052776565548550435972724614541793612778837623695399649245354880712765072167   #getPrime(512)
q = sympy.nextprime(p ^ ((1 << 512) - 1))
c = 14767985399473111932544176852718061186100743117407141435994374261886396781040934632110608219482140465671269958180849886097491653105939368395716596413352563005027867546585191103214650790884720729601171517615620202183534021987618146862260558624458833387692782722514796407503120297235224234298891794056695442287
n = 24852206647750545040640868093921252282805229864862413863025873203291042799096787789288461426555716785288286492530194901130042940279109598071958012303179823645151637759103558737126271435636657767272703908384802528366090871653024192321398785017073393201385586868836278447340624427705360349350604325533927890879
e = 65537

path = (p-1)*(q-1)
d = gmpy2.invert(e, path)
m = pow(c, d, n)
print(long_to_bytes(m))

解得flag为:

XYCTF{3f22f4efe3bbbc71bbcc999a0a622a1a23303cdc}

# Web

## 1.ezhttp

题目:非常ez的http

### 解题过程:

进入网址是一个登录界面,先查看一下源代码

看到提示直接扫目录。

发现了君子协议:robots.txt

打开发现了新的文件

查看 l0g1n.txt文件,找到用户名和密码

那么回到登录界面输入信息,看回显。

那么就bp抓包修改referer请求头:

看回显:

再修改User-Agent头:

User-Agent: XYCTF

继续看回显:

那就用回环地址(127.0.0.1)添加到 xff(X-Forwarded-For)头中:

X-Forwarded-For: 127.0.0.1

回显为:

不让用xff,这里就尝试用Client-ip头代替xff:

Client-ip: 127.0.0.1

成功到下一步:

还需要设置代理,由于xff被禁止,这里用Via头来代替:

Via: ymzx.qq.com

再再再看回显:

这里提示修改cookie,在cookie中添加一个XYCTF参数即可:

cookie: XYCTF=1;GZCTF=CfDJ8NASxn5J2mJJj3Gt7corBUbPAjd7UvBwf-62HPYgrC-I2XsrOFg0aV-HfCev82gylbus2CJSDEvMW1PE7Rvri7CPsTPcpUnRqei0nkyh2fbCwY9gDLJ3UArjOWRtYGyWV5ZgqZi8yF_cqTNp02pL4mmj5sttLXQn1miA8PAgMXZrkLejzVIzC43CTWRtPzKCPEwVrstgYI5FoXUhbx7zUL1Fxv8jckaQuTMLjjU53RArcH9OYMR3B5QpNLxiGW214N5NPjmcG_ZdKhFhRbeGEUCjrcUX_GZXcYaMCCAjApAl5vAMXmAn_7I73YqoATACP7FNvUPciYmVZjWE5Fa70v5RW7c9URntXbHvyiG2e2WvD_5cuqM8ai3fDbMZoaYvntEp6CQS-4R2OfOao3ElVz5wrffpsXXv4DP9EU124QIEMc-cDZnJzQOPnEC3zOnnE0VKmxYCJCPaeRW875zRLxLj6kr-XyB1f3TPE5czsVqtR_Rz2i8arZwnVu6xHg2b7lEup7WS6Awng0MySvZ2pDttcXTrp7-oW2TeUbk_sJvAiVhkAcRlIr_VDxU75EG6BTJK8lNNJ5O1YSo7zAF8oVHQ_twUrDZsCPzx3HaXKBwyz2zeQTqo2A7HcOeqWUS5EMDGrRU4R3Gw58u6qP2KSnY43g1afxX_SlIV5qRgYLFrQe5KNZOV_rdzzEKGaZ8vJGk9mV1as2PDAIl9HKaL5J9-8ftz0GEC_67AhdE9I1i1

拿到flag:

XYCTF{a75074c3-44e8-4d0d-948d-6c5e162b58e3}

## 2.warm up

题目:刚起床没什么状态做题,先简单热个身吧

### 解题过程:

打开网址,页面如下:

看看完代码,是让我们先绕过if判断最后执行输出$level2.

那么就先看看如何绕过过滤:

**==第一个if判断:==**

get方式传入val1,val2两个参数,且两个参数不相等,还要MD5值相等。

这里考的是MD5弱类型绕过。不会的可以看看这篇文章:[https://blog.csdn.net/weixin_43332695/article/details/119349204](https://)

这里可以使用0e绕过:

payload:?val1=QNKCDZO&val2=240610708

//意思就是使两个参数MD5编码后都以0e开头,因为这样会把两个参数当作科学计数法来表示。
//而0的无论多少次方都为0,这样两个参数就相等了。

也可以使用数组绕过:

payload:?val1[]=1&val2[]=2
//MD5()会将数组类型的参数报错并返回false,使两个参数都为false,从而绕过。

看回显(数组的话会有报错,但不影响结果。为了美观我用的0e绕过,):

**==第二个if判断:==**

第二的if是让参数md5等于自身的MD5值,这里有一个字符串是满足的,网上也能找到。

payload:&md5=0e215962017  
//“0e215962017”的MD5值也是以oe开头的所以能绕过过滤。

回显为:

**==第三个if以及嵌套if判断:==**

这里是让XY==XYCTF,而XYCTF="Warm up"。

只要让XY=Warm up就能绕过,但重点是内层的判断,需要让XY不等于“XYCTF_550102591",且需要与"XYCTF_550102591"的MD5值相等。

用在线MD5加密网站加密字符串"XYCTF_550102591"发现它的MD5值是0e开头的。

所以我们需要让XY加密之后也是0e开头。这样的话XY=Warm up就显然不合适了。

**这里就需要再拓展一个知识点,php变量覆盖:**

利用php代码开头的extrace()函数来绕过。简单来讲extrace()是在当前符号表中创建对应的一个变量,后传入的参数值可以覆盖掉之前就已存在的同名参数的值。

可以看看这篇文章:[https://www.cnblogs.com/zzjdbk/p/12985530.html](https://)

这里通过修改XYCTF的值使XY可以传入任意的值。

所以就可以构造:

payload:&XYCTF=QNKCDZO&XY=QNKCDZO 
//QNKCDZO的MD5值是0e开头

看回显:

发现了一个新的php文件。访问它发现:

看完代码,知道需要post传入参数a,利用intval()使if判断为真后,通过preg_replace()函数进行命令执行。

前置知识点:

intval()绕过,可以看这篇文章:[https://blog.csdn.net/wangyuxiang946/article/details/131156104](https://)

preg_replace命令执行,看这里:[https://blog.csdn.net/m0_64815693/article/details/130327529](https://)

咱们一步一步来,

首先传参,

再用preg_match()进行了正则过滤并在前面加了!就是说传参不出现正则匹配的字符就为真,

随后用intval()将参数转为整型。

用HackBar进行post传参,payload:

a[]=123   //这里123有无都无所谓,只要是数组就会报错并执行 返回真。

之后再通过命令执行应该就结束了。

直接看payload:

http://xyctf.top:40064/LLeeevvveeelll222.php?a=/(\S*)/e&b=\1&c=`$_POST[1]`

这里我用$_POST[1],再用POST传参1来传入要执行命令。(因为这道题对GET传参有一些字符上的过滤)

POST传参:

a[]=123&1=ls                    //回显:LLeeevvveeelll222.php index.php next.php 
a[]=123&1=ls  ../../../         //回显:bin core dev etc flag home lib linuxrc media mnt proc root run sbin srv sys tmp usr var
a[]=123&1=cat ../../../flag     //拿到flag

所以flag为:

XYCTF{4e8b09cf-4c24-461d-84b6-34027a86721f} 

  • 9
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值