type
status
date
slug
summary
tags
category
icon
password

漏洞调用分析

一个基于nginx的固件,/etc/nginx/conf.d/ 这里面放着其配置模块
etc\nginx\conf.d\rest.url.conf文件中有5个模块,主要看第三个模块,即文件上传模块
 
注意到当访问/api/operations/ciscosb-file:form-file-upload的时候,仅仅检查了 Authorization字段是否为空
这里我们可以看在前几行有个判断,如果http_authorization 不等于空字符串(即请求中携带了 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_aggregate_form_field :添加聚合字段,包含文件的MD5哈希和大小
  • upload_pass_form_field "^.*$" :传递所有原始表单字段
  • upload_cleanup 400 404 499 500-505 :在指定HTTP状态码时清理上传的文件
  • upload_resumable on :启用可恢复上传功能
 
upload_pass 指明了需要后续处理的函数调用方法
upload_cleanup 如果php出现400 404 499 500-505之类的错误,则删除上传的文件
upload_store 上传文件存放地址
upload_store_access 上传文件的访问权限,user:r是指用户可读
upload_limit_rate 上传限速,如果设置为0则表示不限制
upload_set_form_field 设定额外的表单字段。这里有几个可用的变量:
$upload_file_name 文件原始名字
$upload_field_name 表单的name值
$upload_content_type 文件的类型
$upload_tmp_path 文件上传后的地址
upload_aggregate_form_field 额外的变量,在上传成功后生成
$upload_file_md5 文件的MD5校验值
$upload_file_size 文件大小
upload_pass_form_field 从表单原样转到后端的参数,可以正则表达式表示
官方的例子是upload_pass_form_field "^submit$|^description$";意思是把submit,description这两个字段也原样通过upload_pass传递到后端php处理。如果希望把所有的表单字段都传给后端可以用upload_pass_form_field "^.*$";
 
这是一个标准的Nginx upload上传模块,但很明显这个能执行这个模块的条件过于简单,只要判断http_authorization 不等于空字符串就可以满足条件,
notion image
这是另一个配置文件里的上传模块,这就可以看出这里要满足的要求就比较复杂。
回到之前那个地方,我们可以分析一下这个http_authorization是什么
notion image
全局搜索一下,可以看看到在proxy.conf 文件里
notion image
第6行,可以说明$http_authorizationNginx内置变量,自动捕获客户端请求中的 Authorization 头部值,
 
HTTP Authorization 请求头是一种特殊的 HTTP 头部,允许客户端向服务器传达认证信息,格式如下:
它允许网络技术堆栈中的用户代理(例如,浏览器)向服务器展示认证信息(如令牌、用户名密码对),以完成身份验证过程。这一过程常在服务器需要确认发起请求的客户端是否具备资源访问权限时发生。
 
然后我们看看下面几行的配置,这里在确定好各个upload内容之后通过upload_pass /form-file-upload; 来指明了需要后续处理的函数调用方法,我们寻找一下这个/form-file-upload 再其他什么地方有
notion image
这里可以看在在etc\nginx\conf.d\web.upload.conf 中有location /form-file-upload
notion image
这个Nginx配置块定义了如何处理发送到 /form-file-upload 路径的请求,主要用于处理文件上传功能。
1.include uwsgi_params;
  • 包含uwsgi参数文件,该文件定义了一系列uwsgi协议相关的变量
  • 这些变量将被传递给后端的uwsgi应用服务器
  • 通常包含如 QUERY_STRING 、 REQUEST_METHOD 等HTTP请求变量
2. proxy_buffering off;
  • 关闭代理缓冲
  • 对于文件上传,这很重要,因为它允许数据立即传输到后端,而不在Nginx中缓存
  • 减少内存使用,特别是对于大文件上传
3. uwsgi_modifier1 9;
  • 设置uwsgi协议修饰符1为9
  • 这个特定值(9)表示请求应该作为CGI请求处理
  • uwsgi协议使用修饰符来指定请求的处理方式
4. uwsgi_pass 127.0.0.1:9003;
  • 将请求传递给运行在本地主机(127.0.0.1)端口9003上的uwsgi应用服务器
  • 这是实际处理文件上传的后端服务
  • 使用uwsgi协议进行通信
5. uwsgi_read_timeout 3600;
  • 设置从uwsgi服务器读取响应的超时时间为3600秒(1小时)
  • 对于大文件上传或处理时间较长的操作很重要
  • 防止长时间操作被过早中断
6. uwsgi_send_timeout 3600;
  • 设置向uwsgi服务器发送请求的超时时间为3600秒(1小时)
  • 确保大文件上传有足够时间完成
  • 与读取超时配合,为整个上传过程提供充足时间
 
这里我们可以看出这里启动了uwsgi程序,根据这个可以看出,uwsgi的启动程序指定为/usr/bin/uwsgi-launcher程序
notion image
这里执行了三个初始化程序,我们可以看出,其中有个upload.init ,这里或许就是要满足我们的要求
notion image
这里可以看到第7行的scoket刚好就是之前那个/form-file-upload 的相同端口
notion image
首先是启用了CGi插件来处理CGI脚本,配置了四个工作进程来处理请求,启用了主进程管理工作进程,设置身份,监听9003端口,设置缓冲区大小为4096,将/form-file-upload路径映射到/www/cgi-bin/upload.cgi脚本,允许运行的为.cgi .pl的扩展名,忽略了signal信号。总之就是监听9003端口,运行cgi脚本。接下来的两个ini也是如此,就不过多赘述了,但是看到我们之前分析的/upload的uswgi是发送到9003端口来处理请求的,因此看到上面这个 发现是/www/cgi-bin/upload-cgi来处理请求的,调用的方式是先fork了一个子进程,然后execvp来执行upload.cgi
这个文件是个2进制程序我们在ida里具体分析一下这个文件/www/cgi-bin/upload-cgi
 
这里我们直接看一下漏洞函数在的位置sub_115EC函数
 
这里重点在下面我们可以看到这里程序先是sprintf(s, "mv -f %s %s/%s", s_1, v8, s_2);
然后system(s); ,那么这里就是执行一个system(mv -f s_1 v8/s_2) ,在这个固件的文件里面我们可以看到,www/login.html -> /tmp/www/login.html
notion image
www/login.html 这个文件链接到了 /tmp/www/login.html 这个文件上,这里是这个程序的登录网站
notion image
这里我们结合一下之前提到我们可以想程序传入一个文件,传入的地址在/tmp/upload; 里面,这里我们知道这个登录页面的内容实际在/tmp/www/login.html里,那我们就可以控制上面要执行的命令中的变量,s_1的值为/tmp/upload/xxx (我们上传的文件),v8的值为/tmp/www (要将 v8 控制为 /tmp/www 则要设置 a1 的值为 Portal),s_2的值为login.html,这样程序就会执行 mv -f /tmp/upload/xxx /www/login.html 从而完成对路由器登录界面的篡改。 这里结合sub_115EC(const char *s1, const char *s_1, const char *s_2)函数,可以看出我们要让s1为Portal ,s_1的值为/tmp/upload/xxx,s_2的值为login.html ,看看在main函数中要怎么实现这个要求。
 
 
回到main函数中,
 
这里我们可以看到,我们的目的在于要实现sub_115EC(Portal,/tmp/upload/xxx,login.html) 这个命令,那在这里我们就要让v43的值为Portal,v42的值为/tmp/upload/xxx,v44的值为login.html
 
在这个函数里通过
(multipart_parser_execute)(v38, ptr, v39)
这里这个 multipart_parser_execute 函数是将 POST 报文进行了字段的解析,就大概是做了一个键值对出来,可能用结构体来实现的。
在下面执行到 jsonutil_get_string 函数时,可以把 file.path pathparam 这种字段的值给解析出来,然后给到对应的变量。
这里我们可以分析看出我们的最终的目的是要让,v43, v42, v44这3个变量都放置对应的参数,
v43由v54来,而v54则是通过解析file.path字段得来的
v42由v55的来,而v55则是通过解析pathparam字段得来的
v44看起来比较复制,但其实和上面一样,其中的几个转化基本都是左手倒右手,最终都是由v56来,而v56则是通过解析fileparam字段得来的
 
到这里其实大致就比较清楚了,这里我们要向程序发送一个post报文,其中通过键值对型数据来解析不同的字段包含的参数当作参数,那我们就要使用multipart/form-data (键值对型数据) 方式
这种编码方式,通常是用在客户端向服务端传送大文件数据,如:图片或者文件。
首先来解释下什么它的编码方式,首先会生成一个很长的 boundary 字符串分界线,表明下面的都是表单内容,然后紧接着跟的是表单中的第一个键值对中的名称,而后一个换行,跟着值。然后再生成一个boundary 字符串分界线,用于分割不同的键值。之后就重复以上操作,
 
这里我们再往下看看可以看到有一个检测和我们有关系,
这里对v54进行检测,先判断是否有内容,然后对里面的参数进行检测,进行正则匹配
  • ^:匹配字符串开头
  • /tmp/upload/:字面字符串,必须以这个目录前缀开始。
  • [0-9]{10}:字符类 [0-9] 表示任意数字 0–9{10} 表示恰好 10 次 —— 所以这里要求紧跟 10 个数字。
  • $:匹配字符串末尾(不能有多余字符)。
所以整个字符串必须完全等于 /tmp/upload/ 后面跟 10 个数字,没有多余字符,也不能少。
类似与这种/tmp/upload/0000000000 (这10个数字可以换)
 
集合上面写的我们可以写出一个post报文
 
这里详细解释一下,前面3个分割行里的内容就是multipart/form-data 格式的post报文的内容,name为pathparam ,内容在下面,由我们自己定,然后下面
解释
------------
分隔符(boundary),用来分隔表单字段
Content-Disposition: form-data; name="what";filename="login.html"
这是一个文件部分的头部(因为包含 filename="login.html")。解释:name="what":表单字段名为 what(服务器端通常通过该名字来读取上传的文件)。filename="login.html":客户端声明上传文件的原始文件名是 login.html。服务器可能会使用或重命名该文件。Content-Type: application/octet-stream:声明该文件的 MIME 类型为二进制流,表示没有提供更具体的类型(也可能是文件浏览器/客户端自动设定的)。服务器可以基于内容或扩展名进一步检测 MIME 类型。
Content-Type: application/octet-stream
上传内容的类型,这里当成二进制文件处理(正常文件上传都用这个)
(空行)
空行表示头部和内容的分隔
<title>test</title> <script>alert('debug')</script>
这是上传文件(login.html)的内容的第一行:HTML 标签 <title>test</title>。如果服务器将这个文件原样存储并在浏览器中以 HTML 格式访问,它会把页面标题显示为 test<script>alert('debug')</script>上传文件的另一行:一个 <script> 标签,执行 JS alert('debug')。如果该上传文件被放到可以被浏览器访问的位置(例如直接在 web 根目录),访问该页面会执行此脚本 —— 这是一个跨站脚本(XSS)示例。即:上传的 HTML 文件中包含可执行脚本,一旦用户或管理员在浏览器打开这个文件,会触发弹窗。这说明把用户上传的 HTML 原样上网暴露是危险的。
------------
再次出现 boundary,表示文件数据结束
这里我们用hackber 这个插件来传输这个post报文,
notion image
这里我们要传入的网址根据这个来定,因为漏洞点就在这里,同时我们还有给我们的post报文加上一个头,根据下面可以看出要让http_authorization不能等于0,所以我们还要加上右边这个
notion image
 
关于为什么要0000000001这个文件,这里其实是因为当这个文件运行的时候在/tmp/upload 的目录下会自动生成这个文件,我们直接利用这个文件。我们直接发送这个报文过去。
 
notion image
此时就可以看出,原本的https://192.168.216.130/login.html 页面已经被我们修改了,左上为text,显示的是debug。
 
这里就表示攻击成功,
这里我们用的方法比较质朴,但是有个问题就是,这里当这个文件运行的时候在/tmp/upload 的目录下会自动生成这个文件,这个文件其实是会变得,会随我们发送得报文的次数增加,所以我们看看有没有彻底解决的办法。
 
这里是 winmt 师傅使用的一种比较优雅控制 file.path 的方法,上面提到的方法 file.path 字段是我们主动发过去的,其实报文会根据配置文件来自动来添加一个 xxx.path ,配置文件分析这里其实就说了这个地方(如下图)
notion image
 
这里的 $upload_file_name 就是报文中 Content-Disposition: form-data; name="what";filename="login.html" 的 name 字段,然后在 upload_set_form_field $upload_field_name.path "$upload_tmp_path" 这行代码,会把上传文件的路径记录到这个 xxx.path 字段,这个 xxx 也就是上面的 name 字段的值。 所以实际上报文也可以这么写(如下),这样不需要手动传入 file.path 字段,这个的优点是不知道文件的上传路径依然也能够攻击成功。这次就不再调试了,执行流什么的和上面一样
 
notion image
 
 
感觉这个cve到这里就差不多结束了,花了不少的时间来搞这个,好多地方都没写,太多了
主要照这篇好文来复习的,其中关于如何调试的方法很好,
先把cgi文件改成循环的,卡住然后再gdb里改为正常的从而调试,
需要注意有很多地方会断开,解决办法就是直接下新断点越过那个指令。
有关远程gdb文件编译可以看这个文章
 
放点好文
 
 
 

arm汇编

 
STRB指令
STRB指令用于从源寄存器中将一个8位的字节数据传送到存储器中。该字节数据为源寄存器中的低8位。
STRB R0,[R1] ;将寄存器R0中的字节数据写入以R1为地址的存储器中。
STRB R0,[R1,#8] ;将寄存器R0中的字节数据写入以R1+8为地址的存储器中。
 
LDR指令
LDR指令的格式为:
LDR{条件} 目的寄存器,<存储器地址>
LDR   R0,[R1]                  ;将存储器地址为R1的字数据读入寄存器R0。
LDR   R0,[R1,R2]             ;将存储器地址为R1+R2的字数据读入寄存器R0。
LDR   R0,[R1,#8]             ;将存储器地址为R1+8的字数据读入寄存器R0。
LDR   R0,[R1,R2] !           ;将存储器地址为R1+R2的字数据读入寄存器R0,并将新地址R1+R2写入R1。
LDR   R0,[R1,#8] !          ;将存储器地址为R1+8的字数据读入寄存器R0,并将新地址R1+8写入R1。
LDR   R0,[R1],R2              ;将存储器地址为R1的字数据读入寄存器R0,并将新地址R1+R2写入R1。
LDR   R0,[R1,R2,LSL#2]!   ;将存储器地址为R1+R2×4的字数据读入寄存器R0,并将新地址R1+R2×4写入R1。
LDR   R0,[R1],R2,LSL#2     ;将存储器地址为R1的字数据读入寄存器R0,并将新地址R1+R2×4写入R1。
 
 
 
 
house of系列CVE-2023-34644复现
Loading...
wgiegie
wgiegie
一个苦逼的ctf干饭人🍚
公告
🎉NotionNext 4.5已经上线🎉
-- 感谢您的支持 ---
👏欢迎更新体验👏