Node.js require() RCE复现

前言 前阵子参加了Balsn CTF 2022,有道Node.js的题目叫2linenodejs,个人觉得思路十分巧妙,遂进行了完整的复现,收获颇多。下面是整个复现的过程。 题目代码如下: // 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对象,再使用一个遍历将对象的属性逐一赋给另一个空对象,很明显这里存在原型链污染。 预期思路是在读取JSON是产生异常,然后进入catch执行require('./usage'),通过require()方法实现RCE。 至于如何产生异常,方法是在JSON里面加一个项值为null,这样在遍历时Object.entries(V)为null,再调用forEach就会产生无法读取属性的异常。 require()任意文件包含执行 使用WebStorm对源码进行调试,在catch处下断点,然后传入输入:?{"a":null} ,使用Force Step Into(快捷键Alt+Shift+F7)即可跳转到require()的源码继续调试: 最终定位到Module._load方法: Module._load = function(request, parent, isMain) { let relResolveCacheIdentifier; if (parent) { debug('Module....

Oct. 11, 2022 · 8 min · 1647 words

Git初探

昨天花了两个小时,把廖雪峰老师的Git教程看了一遍,然后再根据网上的一些资料,对Git的一些概念和常用命令做一个归纳。 Git是什么? Git是一个开源的分布式版本管理系统,由Linux之父Linus开发。相比于其他集中式版本管理系统(如SVN、CVS)将整个项目的抓取和提交过程集中在一台中心服务器中,Git中对项目提交修改的每一个开发者都具有一套完整的项目版本库,中心服务器也不再是项目开发的必须项(为了方便提交,很多时候依然会为Git设置中心服务器),开发者在本地对项目进行修改和开发,并将修改提交给其他开发者,其他开发者也各自将自己做的修改提交给其他人,这样就实现了实时的版本管理和修改提交。 常见的应用了Git的网站有Github、Gitee、Gitlab等,它们为开源项目提供免费的Git存储,开发团队可以使用它们来实现版本管理和团队协作。 一些概念 仓库 / 版本库(Repository) 仓库指的是受版本控制管理的文件目录,该目录下的所有文件都受Git的管理,能够查询变更、删除等记录。 工作区(Working tree) 工作区中包含了仓库的工作文件。开发者对工作区的文件进行修改和增删,使用git add命令将修改添加至暂存区,然后使用git commit命令将暂存区的修改提交至仓库。 暂存区(Staging area, 又称Index) 暂存区是工作区用来提交更改(commit)前可以暂存工作区的变化。使用git add命令将修改添加至暂存区。 上面三者之间的联系: [工作区] >>git add>> [暂存区] >>git commit>> [仓库] 头(HEAD) 头是一个象征性的参考,最常用以指向当前选择的分支,同时头也可以指向某个特定的版本。 签出(Checkout) 从仓库中将文件的最新修订版本复制到工作空间。使用git checkout命令,既可以指定特定的版本,也可以指定HEAD指针所在的版本。 分支(Branch) 从主线上分离开的副本,通常对项目的不同功能、不同情况下的开发,需要创建对应的不同的分支。默认分支叫master。 标记(Tags) 标记指的是某个分支某个特定时间点的状态。通过标记,可以很方便的切换到标记时的状态,也可以使用标记来标识某个版本。 常见命令 git init [directory]:在目录directory生成一个Git仓库(--bare参数用于创建一个裸仓库) git add <filename> ...:添加文件,并将修改提交到暂存区 git rm <filename> ...:删除文件,并将修改提交到暂存区 git commit -m <massage>:将暂存区的修改提交到仓库(git commit --allow-empty-message -m ''用于不带message的提交,但不推荐使用) git status:查看当前状态 git diff <filename>:查看当前工作区中文件和暂存区中对应文件的区别 git diff HEAD -- <filename>:查看当前工作区中文件和仓库中对应文件的区别 git log:查看提交历史(--pretty=oneline参数用于美化显示为每条记录一行、--graph参数使数据以图表形式展示) git reflog:查看命令历史 git reset:回退到之前的某个版本(将HEAD指针指向某个版本,之后的所有版本都被删除)(--hard参数同时会清除暂存区和工作区的修改) 用法:git reset 0f4a(版本对应的SHA1值)、git reset HEAD^(相对位置)、git reset master~3(数字表示的相对位置)后面可加文件名仅回退指定文件。 git reset HEAD <filename>:撤销暂存区的修改至工作区 git revert -n <commit>:撤销某一版本对当前版本的修改,并基于此生成一个新的版本(具体和git reset的区别参见这篇文章) git restore --staged <filename>:撤销暂存区的修改至工作区 git checkout -- <filename>:撤销工作区某文件的修改 git restore <filename>:撤销工作区某文件的修改 git checkout <branch>:切换到特定的分支(-b参数表示创建后切换) git switch <branch>:切换到特定的分支 git remote add <name> <url>:添加一个名为name的远程仓库 git push <name> <branch>:将本地仓库的数据推送到远程仓库上(-u用于建立上游引用,将远程仓库和本地仓库关联起来) git pull:从远程仓库抓取数据 git clone <url>:从远程仓库克隆数据至本地 git branch:查看所有分支 git branch <branch>:创建一个分支 git branch -d <branch>:删除一个分支(-D强制删除) git switch -c <branch>:创建一个分支并切换至该分支 git merge:以Fast-Forward模式合并分支(--no-ff参数用于禁用Fast-Forward模式,两者的区别如下图) git stash:储存当时工作目录的状态(保护现场) git stash list:查看所有的stash内容 git stash apply <stash>:恢复某个stash(stash内容保留) git stash drop <stash>:删除某个stash git stash pop <stash>:恢复某个stash并删除 git cherry-pick <commit>:应用某些现有提交引入的更改 git rebase:8太懂,贴一个链接慢慢琢磨https://www....

Feb. 16, 2020 · 2 min · 231 words

DVWA练习记录(三)

相关文章:DVWA练习记录(一)、DVWA练习记录(二) SQL Injection(Blind)(SQL盲注) SQL盲注与一般注入的区别在于,一般的注入攻击者可以直接从页面上看到注入语句的执行结果,而盲注时攻击者通常是无法从显示页面上获取执行结果,甚至连注入语句是否执行都无从得知,因此盲注的难度要比一般注入高。目前网络上现存的SQL注入漏洞大多是SQL盲注。 这篇文章总结了常见的SQL盲注场景: 提交一个导致SQL查询无效时,会返回一个通用错误页面,提交正确则会返回一个内容可被适度控制的页面。 提交一个导致SQL查询无效时,会返回一个通用错误页面,提交正确则会返回一个内容不可控的页面。 提交受损或不正确的SQL既不会产生错误页面,也不会以任何方式影响页面输出。 举一个很形象的例子:有一个机器人,它对于问题只会回答“是”或“不是”,而盲注的目的就是通过问机器人一些问题,根据它返回的结果来判断出我们想要的数据内容。 安全等级Low 界面和一般SQL注入的界面一致,只是输入正确的ID会返回ID存在,而输入错误的ID则会返回ID不存在,不再会返回实际内容: 首先判断能否注入: 输入1' #,返回存在,说明没有报错,存在字符型注入。 接下来猜解用户名和当前数据库名。 这里可以使用基于布尔的盲注和基于时间的盲注两个办法。 基于布尔的盲注就是通过substr()、length()等函数,去查询字段长度、查询字段的每个字符是否为指定值,根据数据库返回的结果确定字段的值。 比如,我们可以问机器人:“数据库名称的长度是4吗?”,“数据库名称的第3个字符是a吗?”,“数据库里面一共有3个表对吗?”类似于这样的问题,就可以逐步判断出数据库的名称以及一些其他的信息。 SELECT first_name, last_name FROM users WHERE user_id = '1' and length(database())=4#'; /* 数据库名称的长度是4吗? */ SELECT first_name, last_name FROM users WHERE user_id = '1' and substr(database(),3,1)='a'#'; /* 数据库名称的第3个字符是a吗? */ SELECT first_name, last_name FROM users WHERE user_id = '1' and (select count(table_name) from information_schema.tables where table_schema=database())=3#'; /* 数据库里面一共有3个表对吗? */ 基于时间的盲注就是借助if条件和sleep()函数,若满足某个条件就延时,反之直接返回。这样就可以通过页面的响应时间来判断条件是否满足。 SELECT first_name, last_name FROM users WHERE user_id = '1' and if(length(database())=4,sleep(5),1)#'; /* 数据库名称的长度是4吗?...

Feb. 9, 2020 · 14 min · 2978 words

DVWA练习记录(二)

相关文章:DVWA练习记录(一)、DVWA练习记录(三) Brute Force(暴力破解) 暴力破解指的是黑客使用穷举法猜解出用户口令,是最为广泛使用的手法之一。在很多情况下,用户会使用不安全的、很容易被猜解的密码,使得这种攻击变得可能。为了提高猜解的成功率,黑客往往还会与社会工程学结合,从不同渠道获取用户的相关信息,如生日、姓名等可能用来作为密码的信息,再基于这些信息构建字典,将其任意组合对密码进行猜解。 理论上说,若网站对口令输入的尝试次数为无限的话,使用穷举法猜解密码总能够成功。所以需要网站进行一定的限制,如多次输入错误限制输入等。 DVWA给出的是一个登录的界面,若登录成功则提示进入受保护区域: 安全等级Low <?php if( isset( $_GET[ 'Login' ] ) ) { // Get username $user = $_GET[ 'username' ]; // Get password $pass = $_GET[ 'password' ]; $pass = md5( $pass ); // Check the database $query = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';"; $result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ?...

Feb. 7, 2020 · 14 min · 2853 words

DVWA练习记录(一)

相关文章:DVWA练习记录(二)、DVWA练习记录(三) 简介 Damn Vulnerable Web Application (DVWA) is a PHP/MySQL web application that is damn vulnerable. Its main goal is to be an aid for security professionals to test their skills and tools in a legal environment, help web developers better understand the processes of securing web applications and to aid both students & teachers to learn about web application security in a controlled class room environment.The aim of DVWA is to practice some of the most common web vulnerabilities, with various levels of difficultly, with a simple straightforward interface....

Feb. 5, 2020 · 12 min · 2460 words

PHP中类的魔术方法总结

PHP中对对象设计了15个非常有用的魔术方法,分别是__construct(), __destruct(), __call(), __callStatic(), __get(), __set(), __isset(), __unset(), __sleep(), __wakeup(), __toString(), __invoke(), __set_state(), __clone() 和 __debugInfo()。这些魔术方法有助于对象在不同的情况下自动的实现一些行为,如初始化对象自动赋值、对象被销毁时发出提示信息等等。下面对这些魔术方法的功能进行简要总结。 __construct() 和 __destruct() __construct()方法是类的构造函数,它在类被实例化为对象时执行。通常用于把一些成员属性初始化为指定值。 __destruct()方法是类的析构函数,它在对象被销毁时执行,通常为对象失去引用时以及程序运行结束时。析构函数没有参数。 <?php class Person{ var $sex; var $name; var $age; function __construct($name = "Nobody", $sex = "Unknown", $age = 1) { $this->name = $name; $this->sex = $sex; $this->age = $age; } function __destruct(){ echo "I'm ".$this->name.", Bye!<br>"; } } $zhangsan = new Person("Zhangsan", "Male", 25); var_dump($zhangsan); $zhangsan = null; ?> // 输出:object(Person)#1 (3) { ["sex"]=> string(4) "Male" ["name"]=> string(8) "Zhangsan" ["age"]=> int(25) } // // 输出:I'm Zhangsan, Bye!...

Oct. 23, 2019 · 5 min · 919 words

PHP面向对象的关键字

面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了;面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。 面向对象是相对于面向过程来讲的,面向对象方法,把相关的数据和方法组织为一个整体来看待,从更高的层次来进行系统建模,更贴近事物的自然运行模式。 PHP为面向对象编程提供了很多的关键字和魔术方法,当然其中一些关键字和魔术方法在其他的面向对象编程语言中也存在,如Java。下面对这些关键字和魔术方法做一个总结。 PHP中的关键字 特殊对象引用$this、$that、self、parent $this 用于在类的实例化对象内部访问这个对象的非静态成员。 $that 用于__clone()魔术方法中,$that为被克隆的原对象,$this为克隆出来的那个对象。 self 用于在类的实例化对象内部访问这个类的静态成员。 parent 用于在某个类的子类对象中访问其父类的成员(通常是静态成员,但有时候可能是实例成员)。 private、protected、public 这三个关键字是用于PHP的访问类型控制的,我们可以使用这些关键字来对类中的属性与方法进行访问权限的设置,并且可以对类进行封装。 private关键字表示私有,使用private的属性和方法对同一个类内里面的所有成员都没有访问的限制,但是在这个类外部的任何位置都不能够访问和操作。 protected关键字表示受保护,使用protected的属性和方法在该类本身以及这个类的子类和父类中没有访问限制,但是在这个类以及它的子类和父类的外部代码中依然不具有对protected属性和方法的访问权限。 public关键字表示公共,也就是说使用public的属性和方法在程序的任何位置都可以被访问和操作。在PHP中,如果没有为成员指定访问控制关键字,那么默认这个成员为public。 class MyClass { public $public = 'Public'; protected $protected = 'Protected'; private $private = 'Private'; function printHello() { echo $this->public; echo $this->protected; echo $this->private; } } $obj = new MyClass(); echo $obj->public; // 这行能被正常执行 echo $obj->protected; // 这行会产生一个致命错误 echo $obj->private; // 这行也会产生一个致命错误 $obj->printHello(); // 输出 Public、Protected 和 Private class MyClass2 extends MyClass { // 可以对 public 和 protected 进行重定义,但 private 而不能 protected $protected = 'Protected2'; function printHello() { echo $this->public; echo $this->protected; echo $this->private; } } $obj2 = new MyClass2(); echo $obj2->public; // 这行能被正常执行 echo $obj2->private; // 未定义 private echo $obj2->protected; // 这行会产生一个致命错误 $obj2->printHello(); // 输出 Public、Protected2 和 未定义 private 的错误 避免踩坑:在验证PHP的访问控制机制时,需要在PHP配置文件php....

Oct. 23, 2019 · 2 min · 401 words

PHP中对数组的操作

数组的声明 PHP中有两种数组:索引数组和关联数组。索引数组的索引是从0开始递增的数字,由程序自动生成;关联数组使用字符串作为索引值,由用户自行输入。 初始化时直接赋值 //索引数组 $example[0] = "a"; $example[1] = "b"; $example[2] = "c"; //关联数组 $example_1["a"] = "1"; $example_1["b"] = "2"; $example_1["c"] = "3"; 若要按默认索引顺序声明索引数组,可以不用填入索引值,程序自动按声明顺序为键值建立索引: $example[] = "a"; //索引为0 $example[] = "b"; //索引为1 $example[] = "c"; //索引为2 通过array()函数创建 //索引数组 $example = array("a", "b", "c"); //关联数组 $example_1 = array("a" => "1", "b" => "2", "c" => "3"); 多维数组的创建使用array()函数嵌套完成: $example = array( "array1" => array("a", "b", "c"), "array2" => array("d", "e", "f"), "array3" => array("g", "h", "i") ) 数组的遍历 使用for循环遍历 <?...

Oct. 19, 2019 · 8 min · 1493 words