GitLab部分漏洞分析

GDK运行+调试环境搭建 GDK全称GitLab Development Kit,是GitLab官方为了方便开发者为GitLab开源项目贡献开源代码而开发的一键式GitLab运行+调试部署工具,在需要对GitLab漏洞进行调试和分析的情况下,除去使用GitLab的Docker镜像也可以利用GDK来搭建环境,地址为https://gitlab.com/gitlab-org/gitlab-development-kit。 如果需要分析的是GitLab最新的代码,则使用GDK官方文档里的安装步骤就可以一步到位。但如果利用GDK进行漏洞复现与分析,往往需要使用到老版本的GitLab代码,此时如果使用最新的GDK部署的话,则会不可避免的出现很多依赖相关的问题(如Ruby版本要求不一致,一些Ruby依赖包不符合版本要求等),在整个部署的时候会遇到很多坑。下面大概说一下一个较为可行的部署流程,以及我在部署过程中的一些问题。 此处以v15.1.0-ee版本的部署为例。首先Clone一下GDK: 1 git clone https://gitlab.com/gitlab-org/gitlab-development-kit.git gdk GDK版本调整和初始化 接下来需要查看GitLab版本的发布时间,将GDK也Checkout到对应版本发布日期附近的提交处,这样可以保证GDK使用的Ruby版本和GitLab一致,避免后续安装过程中因为Ruby大版本不一致导致的各种依赖问题。 查看提交记录可以发现v15.1.0-ee的发布时间为2022年6月21日,所以我将GDK调整至2022年7月26日的提交处:77019f1204a3bbcb44bac37bfd0da4059aa130e9。只要保证GDK的Ruby依赖版本不要和GitLab的相差过大即可。 切换版本之后,与官方文档中手动部署的步骤一致,使用make bootstrap初始化GDK,安装GDK相关的依赖。在这篇文章中提到了使用一键脚本部署后再Checkout GitLab到对应版本的方式,经过测试我发现如果版本相差过大,依赖问题依然会存在,并且可能存在数据库结构不一样的问题,因此此处更好的解决方案是手动Clone GitLab仓库,自行Checkout之后再开始部署GDK,如下。 1 2 3 4 5 git clone https://gitlab.com/gitlab-org/gitlab.git gdk/gitlab cd gdk/gitlab git checkout v15.1.0-ee cd .. gdk install 在gdk install的过程中可能会出现各种各样奇奇怪怪的问题,我主要把问题归结于以下两类: 由于GDK调整了到早期版本,有些Bug还没修,通过Google和GDK的Issues大部分可以找到解决方案; 一些Native Extension的编译问题,如OpenSSL、gpgme等等;(gpgme问题出现的频率最高,主要表现为gpgme编译失败,解决方案是不通过bundler安装gpgme而是使用gem install gpgme -- --use-system-libraries手动安装) Ruby版本不同导致的依赖问题,如有些软件包的老版本不再被新版Ruby支持,又或是一些新版本的软件包不被老版本的Ruby支持。(这里因为前面已经通过Checkout把GDK和GitLab的依赖版本调整到尽可能一致了,所以这里不会有太大的问题) 一些配置的修改 gdk install成功跑完之后,就相当于脚本安装结束了。接下来修改gitlab/config/gitlab.yml配置文件,修改监听的IP地址,以及关闭Webpack的开发模式,可以减少一点占用: 1 2 3 4 5 6 7 8 gitlab: host: 0.0.0.0 port: 3000 https: false webpack: dev_server: enabled: false 修改完了配置文件,需要重新编译一下前端资源: ...

Jul. 22, 2023 · 5 min · 990 words · Jiekang Hu

NeSE三月升级赛WriteUp (Web)

又是一个XSS题,Docker里起了一个Web一个Bot一个Redis,Web使用Java写的,用的Eclipse的Jetty服务器,上层是Micronaut微服务框架来的,整体打包成一个JAR。 附件地址:CTF-Chal/fancy-notes.zip (github.com) 一开始还以为是Java相关的漏洞,随手看了下反编译,发现好像除了题目是Java写的之外和Java没啥关系,同理Redis也只是个用来传数据的中间媒介,似乎也利用不了什么漏洞。于是继续关注题目本身的逻辑。 先看Bot做了啥: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 const puppeteer = require("puppeteer"); const Redis = require('ioredis'); const connection = new Redis(6379, process.env.REDIS_HOST ?? "127.0.0.1"); const browser_option = { headless: true, args: [ '--no-sandbox', '--disable-gpu', '--js-flags="--noexpose_wasm --jitless"', ], executablePath: "google-chrome-stable" }; const MAIN_SITE = process.env.MAIN_SITE ?? "http://127.0.0.1:8000" const FLAG = process.env.FLAG ?? "flag{test}" const sleep = (delay) => { return new Promise((resolve) => setTimeout(resolve, delay)) } async function browse(url) { console.log(`[+] browsing ${url} started`) const browser = await puppeteer.launch(browser_option); const page = await browser.newPage(); page.on('dialog', async (dialog) => { await dialog.dismiss(); }); try { await page.goto(MAIN_SITE, { timeout: 3000, waitUntil: 'domcontentloaded' }); await sleep(1000); await page.setCookie({ name: "FLAG", value: FLAG, domain: new URL(MAIN_SITE).hostname, path: "/", secure: false, httpOnly: true }); await page.goto(url, { timeout: 3000, waitUntil: 'domcontentloaded' }); await sleep(5000); console.log(await page.cookies(MAIN_SITE)); } catch (err) { console.log(err); } finally { await page.close(); await browser.close(); } console.log(`[+] browsing ${url} finished`) } const handler = async () => { console.log('[+] Starting bot'); while (true) { console.log("[+] Working ") connection.blpop('urls', 0, async (err, message) => { try { let url = message[1]; let parsed = new URL(url); if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') { throw new Error('Invalid protocol'); } else { console.log('[+] Visiting ' + url); await browse(url); } await sleep(3000); } catch (e) { } }); await sleep(3000) } } handler(); 能看见Bot首先访问了网站的首页,然后把Flag写进了Cookie里面,加了HTTP Only(这个是重点,等会会考),然后就会访问提交给Bot的URL了。Bot是死循环从Redis里面读URL,网站里也有相关的逻辑。 ...

Mar. 19, 2023 · 7 min · 1362 words · Jiekang Hu

HXPCTF 2023 Web复现

valentine Web签到题,考的是Node.js的ejs模板库命令执行。首先给出源码,里面的库版本都是最新的: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 var express = require('express'); var bodyParser = require('body-parser') const crypto = require("crypto"); var path = require('path'); const fs = require('fs'); var app = express(); viewsFolder = path.join(__dirname, 'views'); if (!fs.existsSync(viewsFolder)) { fs.mkdirSync(viewsFolder); } app.set('views', viewsFolder); app.set('view engine', 'ejs'); app.use(bodyParser.urlencoded({ extended: false })) app.post('/template', function(req, res) { let tmpl = req.body.tmpl; let i = -1; while((i = tmpl.indexOf("<%", i+1)) >= 0) { if (tmpl.substring(i, i+11) !== "<%= name %>") { res.status(400).send({message:"Only '<%= name %>' is allowed."}); return; } } let uuid; do { uuid = crypto.randomUUID(); } while (fs.existsSync(`views/${uuid}.ejs`)) try { fs.writeFileSync(`views/${uuid}.ejs`, tmpl); } catch(err) { res.status(500).send("Failed to write Valentine's card"); return; } let name = req.body.name ?? ''; return res.redirect(`/${uuid}?name=${name}`); }); app.get('/:template', function(req, res) { let query = req.query; let template = req.params.template if (!/^[0-9A-F]{8}-[0-9A-F]{4}-[4][0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i.test(template)) { res.status(400).send("Not a valid card id") return; } if (!fs.existsSync(`views/${template}.ejs`)) { res.status(400).send('Valentine\'s card does not exist') return; } if (!query['name']) { query['name'] = '' } return res.render(template, query); }); app.get('/', function(req, res) { return res.sendFile('./index.html', {root: __dirname}); }); app.listen(process.env.PORT || 3000); 功能点就是可以自定义模板内容,生成并渲染,但是对模板中ejs的tag进行了严格限制,只能存在<%= name %>这一种tag,否则就会被ban。 ...

Mar. 12, 2023 · 5 min · 978 words · Jiekang Hu

NeSE十二月升级赛WriteUp (Web)

一个硬核的XSS题,收获相当大(还得感谢CrumbledWall师傅点拨了我几次) 开局给了后端的源码,能看见里面用了腾讯的COS对象存储,可以上传文件到上面,docker-compose.yml里面还有一个名为的bot的服务但没有给出源码,能看出来后端有个接口会去连接这个bot。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 const express = require("express") const isJpg = require('is-jpg') const isPng = require('is-png') const isWebp = require('is-webp') const fs = require('fs') const fileUpload = require('express-fileupload') const bodyParser = require('body-parser') const net = require('net') const crypto = require("crypto") const https = require('https') var COS = require('cos-nodejs-sdk-v5') const app = express() const BOT_HOST = 'bot' const BOT_PORT = 8080 app.use(fileUpload({ limits: { fileSize: 2 * 1024 * 1024 // 2 MB }, abortOnLimit: true })) app.use(bodyParser.urlencoded({ extended: true })) async function uploadFileToCOS(file, fileName) { var cos = new COS({ SecretId: 'xxxxxxxxxxxxxx', SecretKey: 'xxxxxxxxxxxxxx' }); cos.putObject({ Bucket: 'nese-1300117079', /* 填入您自己的存储桶,必须字段 */ Region: 'ap-beijing', /* 存储桶所在地域,例如ap-beijing,必须字段 */ Key: fileName, /* 存储在桶里的对象键(例如1.jpg,a/b/test.txt),必须字段 */ Body: Buffer.from(file.data), /* 必须 */ }, function(err, data) { try { if (data.statusCode == 200) { return "File uploaded successfully! You can visit the file at " + data.Location } else { return "Failed!" } } catch (err) { return "Failed!" } }) } app.get("/", (req, res) => { fs.readFile('index.html', 'utf-8', (err, data) => { if (err) throw err let generateNonce = crypto.randomBytes(16).toString("hex") const dataReplaced = data.replace(/nonce_must_be_replaced/g, generateNonce) res.write(dataReplaced) res.end() }) }) app.post('/upload/:md5', async (req, res) => { if (!req.files) return res.status(400).send(response('No files were uploaded.')) const md5Regex = /^[0-9a-fA-F]{32}$/ const srcUrl = "https://nese-1300117079.cos.ap-beijing.myqcloud.com/" var suffix = req.files.newImage.name.split('.').slice(-1) var fileName = req.params.md5 + '.' + suffix if ((isPng(req.files.newImage.data) || isJpg(req.files.newImage.data) || isWebp(req.files.newImage.data)) && md5Regex.test(req.params.md5) && suffix != 'js' && suffix != 'html' && suffix != 'htm') { response = await uploadFileToCOS(req.files.newImage, fileName) res.send(response) } else { res.send("Failed!") } }) app.post("/report", function (req, res) { const { url } = req.body if (url.search('https://challenge/') != 0) { return res.status(400).send('Invalid URL') } console.log(`[+] Sending ${url} to bot`) try { const client = net.connect(BOT_PORT, BOT_HOST, () => { client.write(url) }) let response = '' client.on('data', data => { response += data.toString() client.end() }) client.on('end', () => res.send(response)) } catch (e) { console.log(e) res.status(500).send('Something is wrong...') } }) const credentials = { key: fs.readFileSync('/opt/credentials/privatekey.pem'), cert: fs.readFileSync('/opt/credentials/certificate.pem') }; const httpsServer = https.createServer(credentials, app) httpsServer.listen(443) /* app.listen(8000, () => { console.log(`App 🚀 @ http://localhost:8000`) })*/ 寻找前端可控点 接下来看前端。首先发现前端有个异常严格的CSP,这里面的nonce每次刷新都会生成一个新的。 ...

Dec. 20, 2022 · 6 min · 1267 words · Jiekang Hu

UCORE Lab 2

UCORE的段页式内存布局 从UCORE启动到物理内存管理的初始化结束,一共加载了3次GDT,实现了由单纯的分段到段页式内存管理的布局。 在内核链接脚本中,内核的加载地址由Lab1中的0x100000改为了0xC0100000: 1 2 3 4 5 6 7 8 9 10 OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386") OUTPUT_ARCH(i386) ENTRY(kern_entry) SECTIONS { /* Load the kernel at this address: "." means the current address */ . = 0xC0100000; ...... } 根据文档里提到的,这个地址为虚拟地址,后续通过分段和分页来映射到物理地址,实际内核还是放在0x100000的物理地址开始的。这个从bootmain函数中可以看得出来,在加载ELF段的时候对va进行了与操作,去除了地址中的高8位数据,也就是把C0给抹除了: 1 2 3 4 5 6 7 8 9 10 // load each program segment (ignores ph flags) ph = (struct proghdr *)((uintptr_t)ELFHDR + ELFHDR->e_phoff); eph = ph + ELFHDR->e_phnum; for (; ph < eph; ph ++) { readseg(ph->p_va & 0xFFFFFF, ph->p_memsz, ph->p_offset); } // call the entry point from the ELF header // note: does not return ((void (*)(void))(ELFHDR->e_entry & 0xFFFFFF))(); 1 - 准备进入内核代码 在boot/bootasm.S中,对GDT进行了第一次设置: ...

Dec. 2, 2022 · 8 min · 1680 words · Jiekang Hu

N1CTF 2022 Web 复现

Easy_S2 这题主要考察的是Struts2的路径匹配规则以及Java Web中的Security-Constraint安全约束选项。题目附件是一个.war包,将其解压之后可以直接通过Tomcat部署,代码实现也很简单。通过web.xml可以了解到整个网站的路由都被导向了中间Struts2,同时有两条安全约束项: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 <?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1"> <filter> <filter-name>struts2</filter-name> <filter-class>org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter</filter-class> </filter> <!-- 整个网站的路由都由Struts2处理 --> <filter-mapping> <filter-name>struts2</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- /img/* 和 /index.jsp 不需要认证即可访问 --> <security-constraint> <display-name>pass-static</display-name> <web-resource-collection> <web-resource-name>static</web-resource-name> <url-pattern>/img/*</url-pattern> <url-pattern>/index.jsp</url-pattern> </web-resource-collection> </security-constraint> <security-constraint> <display-name>interceptor</display-name> <web-resource-collection> <web-resource-name>flag</web-resource-name> <url-pattern>/*</url-pattern> </web-resource-collection> <auth-constraint/> <!-- 剩下的路由需要认证才能访问 --> </security-constraint> <login-config> <!-- 认证方式为Basic Auth --> <auth-method>BASIC</auth-method> </login-config> </web-app> 因为这两条安全约束项的存在,访问被限制为只有路由/img/*和/index.jsp可以直接访问,其余的若没有经过认证则会返回403。但现在没有有关认证用户密码相关的任何信息,于是先看看其他的配置文件。在WEB-INF/classes/struts.xml中,定义了Struts2的路由规则: ...

Nov. 21, 2022 · 8 min · 1642 words · Jiekang Hu

UCORE Lab 1

Ex.1 Makefile 狗都不看 操作系统镜像文件ucore.img是如何一步一步生成的? make -n可以输出具体执行的命令: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 # Compile Kernel Sources echo + cc kern/init/init.c gcc -Ikern/init/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/init/init.c -o obj/kern/init/init.o echo + cc kern/libs/stdio.c gcc -Ikern/libs/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/libs/stdio.c -o obj/kern/libs/stdio.o echo + cc kern/libs/readline.c gcc -Ikern/libs/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/libs/readline.c -o obj/kern/libs/readline.o echo + cc kern/debug/panic.c gcc -Ikern/debug/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/debug/panic.c -o obj/kern/debug/panic.o echo + cc kern/debug/kdebug.c gcc -Ikern/debug/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/debug/kdebug.c -o obj/kern/debug/kdebug.o echo + cc kern/debug/kmonitor.c gcc -Ikern/debug/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/debug/kmonitor.c -o obj/kern/debug/kmonitor.o echo + cc kern/driver/clock.c gcc -Ikern/driver/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/driver/clock.c -o obj/kern/driver/clock.o echo + cc kern/driver/console.c gcc -Ikern/driver/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/driver/console.c -o obj/kern/driver/console.o echo + cc kern/driver/picirq.c gcc -Ikern/driver/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/driver/picirq.c -o obj/kern/driver/picirq.o echo + cc kern/driver/intr.c gcc -Ikern/driver/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/driver/intr.c -o obj/kern/driver/intr.o echo + cc kern/trap/trap.c gcc -Ikern/trap/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/trap/trap.c -o obj/kern/trap/trap.o echo + cc kern/trap/vectors.S gcc -Ikern/trap/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/trap/vectors.S -o obj/kern/trap/vectors.o echo + cc kern/trap/trapentry.S gcc -Ikern/trap/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/trap/trapentry.S -o obj/kern/trap/trapentry.o echo + cc kern/mm/pmm.c gcc -Ikern/mm/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/mm/pmm.c -o obj/kern/mm/pmm.o # Compile Libs echo + cc libs/string.c gcc -Ilibs/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -c libs/string.c -o obj/libs/string.o echo + cc libs/printfmt.c gcc -Ilibs/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -c libs/printfmt.c -o obj/libs/printfmt.o # Link Kernel Objects and Libs Objects to Kernel Binary File mkdir -p bin/ echo + ld bin/kernel ld -m elf_i386 -nostdlib -T tools/kernel.ld -o bin/kernel obj/kern/init/init.o obj/kern/libs/stdio.o obj/kern/libs/readline.o obj/kern/debug/panic.o obj/kern/debug/kdebug.o obj/kern/debug/kmonitor.o obj/kern/driver/clock.o obj/kern/driver/console.o obj/kern/driver/picirq.o obj/kern/driver/intr.o obj/kern/trap/trap.o obj/kern/trap/vectors.o obj/kern/trap/trapentry.o obj/kern/mm/pmm.o obj/libs/string.o obj/libs/printfmt.o # Generate Disassembly and Symbol Table objdump -S bin/kernel > obj/kernel.asm objdump -t bin/kernel | sed '1,/SYMBOL TABLE/d; s/ .* / /; /^$/d' > obj/kernel.sym # Compile and Link Bootloader echo + cc boot/bootasm.S gcc -Iboot/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Os -nostdinc -c boot/bootasm.S -o obj/boot/bootasm.o echo + cc boot/bootmain.c gcc -Iboot/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Os -nostdinc -c boot/bootmain.c -o obj/boot/bootmain.o echo + cc tools/sign.c gcc -Itools/ -g -Wall -O2 -c tools/sign.c -o obj/sign/tools/sign.o gcc -g -Wall -O2 obj/sign/tools/sign.o -o bin/sign echo + ld bin/bootblock ld -m elf_i386 -nostdlib -N -e start -Ttext 0x7C00 obj/boot/bootasm.o obj/boot/bootmain.o -o obj/bootblock.o # Generate Disassembly of Bootloader objdump -S obj/bootblock.o > obj/bootblock.asm # Remove all symbol and relocation information objcopy -S -O binary obj/bootblock.o obj/bootblock.out # Append 0x55AA(2 Bytes) on bootblock bin/sign obj/bootblock.out bin/bootblock # Create UCORE Image dd if=/dev/zero of=bin/ucore.img count=10000 dd if=bin/bootblock of=bin/ucore.img conv=notrunc dd if=bin/kernel of=bin/ucore.img seek=1 conv=notrunc 生成镜像的步骤 编译内核代码,即kern/目录下的源文件 编译公用库代码,即libs/目录下的源文件 将内核源代码的目标文件与公用库目标文件链接在一起,形成Kernel二进制文件 调用objdump生成Kernel的反汇编和符号表文件 编译Bootloader代码 链接Bootloader目标文件 调用objdump生成Bootloader的反汇编 调用objcopy去除Bootloader二进制文件中的符号和重定位信息 调用sign程序,在Bootloader的末尾添加两个字节 使用dd命令创建UCORE镜像 GCC编译参数 (使用gcc -v —help查看详细的参数释义) ...

Oct. 24, 2022 · 17 min · 3555 words · Jiekang Hu

Node.js require() RCE复现

前言 前阵子参加了Balsn CTF 2022,有道Node.js的题目叫2linenodejs,个人觉得思路十分巧妙,遂进行了完整的复现,收获颇多。下面是整个复现的过程。 题目代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 // server.js process.stdin.setEncoding('utf-8'); process.stdin.on('readable', () => { try{ console.log('HTTP/1.1 200 OK\nContent-Type: text/html\nConnection: Close\n'); const json = process.stdin.read().match(/\?(.*?)\ /)?.[1], obj = JSON.parse(json); console.log(`JSON: ${json}, Object:`, require('./index')(obj, {})); }catch{ require('./usage') }finally{ process.exit(); } }); // index.js module.exports=(O,o) => (Object.entries(O).forEach(([K,V])=>Object.entries(V).forEach(([k,v])=>(o[K]=o[K]||{},o[K][k]=v))), o); // usage.js console.log('Validate your JSON with <a href="/?{}">query</a>'); 在try block里面将输入的JSON字符串转换为JavaScript对象,再使用一个遍历将对象的属性逐一赋给另一个空对象,很明显这里存在原型链污染。 ...

Oct. 11, 2022 · 11 min · 2142 words · Jiekang Hu