WEBVTT

00:01.500 --> 00:03.320
Hi, everyone.

00:03.320 --> 00:05.240
My name is Feng Xiao from Georgia Tech.

00:05.240 --> 00:14.800
It seems like I'm the first DEF CON safe mode main talk, and I really hope everyone can enjoy attending my work.

00:14.920 --> 00:22.880
So this is, of course, not only my work, but also the work of my wonderful co-authors from Georgia Tech and Texas A&M.

00:22.880 --> 00:28.480
Today, I'm going to talk about some interesting new vulnerabilities in the Node.js ecosystems.

00:29.360 --> 00:34.840
Before the talk normally begins, please let me introduce myself a little more.

00:34.920 --> 00:38.280
I am a CS PhD student at Georgia Tech.

00:38.280 --> 00:44.200
My research goal is about building automatic systems to detect and exploit vulnerabilities.

00:44.300 --> 00:53.940
We want the tools to exploit vulnerabilities because we want the people to know the existence and the consequence of their security bugs.

00:54.120 --> 00:58.040
And I do research in the web and application security.

00:58.040 --> 01:06.340
But I'm also researching security problems in other areas, such as the software defining networks and x86 virtualizations.

01:07.100 --> 01:10.540
Okay, so this is the topic I'm going to cover today.

01:10.540 --> 01:13.820
The talk will be divided into three parts.

01:14.280 --> 01:20.480
First, I will introduce the technical details of the new vulnerabilities and discuss their exploitation.

01:20.840 --> 01:34.660
Then, I will talk more about the bug-finding parts, which is about the lessons and the insights from building one kind of a tool to detect and exploit HPA.

01:34.960 --> 01:43.800
And in the last part, I will give an impact analysis of the new risk and some evaluation data about the tools.

01:45.300 --> 01:51.140
So first of all, let's take a quick look at the vulnerabilities we found during our research.

01:51.920 --> 02:02.640
So in total, we discovered 13 zero-day vulnerabilities from widely used programs, such as MongoDB official drivers, class validators.

02:02.900 --> 02:12.640
And these bugs can be exploited to launch serious attacks, such as leaking credential data, bypassing security checks, and denial of services.

02:13.120 --> 02:23.280
So before we touch the technical details of these vulnerabilities, let's have a brief background introduction about Node.js.

02:24.260 --> 02:28.480
Node.js is for executing JavaScript code outside of browsers.

02:28.900 --> 02:33.140
The picture on the right is the overall system diagram of Node.js.

02:33.260 --> 02:45.300
To interrupt and execute JavaScript, the Node.js implements a runtime engine based on Chrome V8 to satisfy the needs of the server-side languages.

02:46.160 --> 02:52.940
The runtime engine also provides a set of API to let JavaScript interact with the host environments.

02:53.280 --> 03:01.980
By providing such APIs, the JavaScript code can access the host environments like any other server-side languages.

03:01.980 --> 03:07.980
For example, it can read and write file systems or execute system commands.

03:08.280 --> 03:10.560
So Node.js is pretty powerful.

03:12.100 --> 03:15.520
Nowadays, many websites are deploying Node.js.

03:15.520 --> 03:21.300
For example, Node.js are intensively used in companies like PayPal and LinkedIn.

03:21.300 --> 03:28.740
Also, we are all using a lot of electronic apps such as Skype or Skogs, Discord.

03:28.740 --> 03:33.280
And such electronic apps are also based on Node.js runtime.

03:34.540 --> 03:37.640
We've seen so many Node.js applications.

03:37.640 --> 03:42.580
The web-based apps are one of the most common types of Node.js programs.

03:42.940 --> 03:53.560
For those web-based applications, taking the communication data into object representations like JSON is pretty common.

03:53.780 --> 03:56.100
And this feature is convenient.

03:56.660 --> 04:03.660
For example, Node.js can use this feature to send and receive very complex data structure.

04:03.760 --> 04:14.060
From the monthly download statistic picture on the right, we can have an idea of how the object sharing is being supported and used by the Node.js ecosystems.

04:15.740 --> 04:22.820
The diagram demonstrates how the object sharing is being used in the Node.js ecosystem.

04:22.860 --> 04:28.880
There are two major methods of serialization of object data.

04:28.880 --> 04:34.920
The first is the query-stream-based serialization, and the second is the JSON-based serialization.

04:34.940 --> 04:49.080
As shown in the picture, if the user wants to update the age information in the Node.js web, he can send his data either via a standard query stream in the URL or a JSON stream in the request body.

04:49.140 --> 05:02.720
Upon receiving the request, the web application will convert the JSON data or the query stream data into an object so that the object can further propagate in the program logics.

05:03.380 --> 05:09.280
Okay, so basically this is how the object sharing is carried out in Node.js ecosystems.

05:09.280 --> 05:25.680
Usually, if we want to evaluate the security of such a program, we may want to inject different payloads into the age field so that we can try to trigger certain vulnerabilities such as SQL injections or cross-site scripts.

05:26.660 --> 05:32.360
However, what if we choose not to test the existing data field?

05:32.800 --> 05:42.860
Since we can pass an object into the program, what will happen if we inject additional properties that are not expected to be received by the server program?

05:42.860 --> 05:58.220
In particular, if an attacker can send the properties that forge or override the certain internal program states, the attacker may easily obtain dangerous abilities to manipulate the key program logics.

05:59.500 --> 06:04.820
So we are going to introduce the hidden property abusing, HPA.

06:04.820 --> 06:12.680
So the hidden property abusing leverages the object sharing in Node.js to tamper or forge critical program states.

06:12.680 --> 06:24.200
We call the additional properties we inject as the hidden properties because these properties are kind of like some hidden parameters which is valid to the endpoint user API.

06:24.620 --> 06:29.480
These parameters are associated with certain internal program states.

06:29.480 --> 06:36.360
However, nobody knows their existence until an attacker leverages HPA to tamper the states.

06:37.000 --> 06:47.240
In this talk, we mainly focus on the server-side scenarios where a remote attacker wants to attack the Node.js web applications or some microservices.

06:47.420 --> 06:56.980
To explore the vulnerabilities, the attacker will access the legitimate interfaces such as the web API endpoint to send his payloads.

06:56.980 --> 07:07.040
In most cases, the attacker payloads should be in the form of planned object, which is the simplest object representation in Node.js.

07:08.860 --> 07:15.280
During our research, we discovered two types of typical attack vectors of HPA.

07:15.660 --> 07:20.400
We call the first one as the app-specific attribute manipulation.

07:20.520 --> 07:27.000
This one is for manipulating certain internal properties defined by the application themselves.

07:27.000 --> 07:33.180
Search internal properties are supposed to be initialized and managed by their internal functions.

07:33.180 --> 07:39.140
However, they usually represent certain internal states of the programs.

07:39.240 --> 07:49.060
So as shown in the picture, the INIT row is an internal function that is responsible for maintaining the access right on the user object.

07:49.300 --> 07:56.360
However, with HPA, the attacker can propagate a conflicting name property to the user object.

07:56.360 --> 08:01.720
And thus control the internal states of the program.

08:01.900 --> 08:08.260
As shown in the picture, the program also provides an API called update.

08:08.260 --> 08:10.740
This is for external usage.

08:11.060 --> 08:29.660
However, if a malicious user injects additional key-value pair, which is access and admin in the picture to the API, then the additional properties will override the existing access right.

08:30.000 --> 08:42.760
So this payload is pretty useful when we want to abuse certain concrete logics in large applications, such as some older information or user-privileged any management logics.

08:43.340 --> 08:50.240
Also, HPA can target some unique JavaScript schemes, such as prototyping.

08:50.240 --> 08:55.420
We call the second attack scenarios as prototype inheritance hijacking.

08:56.220 --> 09:00.700
In JavaScript, every object has a link to a prototype object.

09:00.860 --> 09:17.260
When the JavaScript code wants to access a property of an object, the property will not only be searched on the object, but also the prototype of the object, and even the prototype of the prototype, until a property with a matching name is found.

09:17.740 --> 09:29.160
As shown in the picture, when the JavaScript code wants to get the constructor property from the input object, it will first search locally within the input object.

09:29.160 --> 09:40.060
Since there is no property named constructor here, the code will continually search to his prototype, where the constructor is really located.

09:40.760 --> 09:49.700
So with HPA, we can hijack the inheritance chain and forge our own payloads as the internal properties on the chain.

09:49.900 --> 09:57.120
As shown in the picture, if we inject a property named constructor, the search process will be very different.

09:57.120 --> 10:09.180
Since there is already a property named constructor within the input object, the search will immediately stop and end up with retaining a user-controlled value.

10:09.300 --> 10:18.600
As demonstrated in the red circle, the value of the constructor will become a string, rig and multi, rather than a normal JavaScript function.

10:19.280 --> 10:34.700
So the second attack vector is really useful because we found many JavaScript developers tend to trust the properties inherited from prototypes and make many security-sensitive decisions based on them.

10:34.940 --> 10:42.220
Also, we should be aware of the differences between these attack vectors with the prototype pollution.

10:42.240 --> 10:45.900
The two attacks are totally different.

10:45.900 --> 10:52.540
The prototype pollution, as the name suggests, is about tampering the prototype object.

10:52.540 --> 10:58.760
However, our attack vector does not modify the prototype object.

11:00.650 --> 11:12.610
The root cause of the HPA is that the Node.js fails to isolate the unsafe object, such as the user input, from the critical internal states.

11:13.030 --> 11:29.770
So, to make a clear classification, the HPA can be seen as a new security risk under the Common Weakness Category 915, whose child variants are all about improper modification of the dynamic object attributes.

11:30.290 --> 11:40.730
As shown in the hierarchy tree on the right, there are some similar issues on other language platforms, such as Ruby Math Assignment, the PHP Object Injections.

11:40.870 --> 11:51.930
Although these variants share the same behavior feature dominated by 915, they all have their own data patterns due to the language differences.

11:52.170 --> 12:02.810
For example, the Ruby Math Assignment is a set of vulnerabilities discovered in widely used Ruby web application framework called Ruby on Rails.

12:03.170 --> 12:08.190
Unlike HPA, the attacker does not pass objects into the program.

12:08.190 --> 12:20.110
Instead, the attacker abuses a framework-specific assignment feature in Ruby on Rails to modify certain existing objects on the right side of the assignment.

12:20.170 --> 12:24.150
And the payloads between the two attacks are also different.

12:24.190 --> 12:27.430
The Math Assignment payloads are literal value.

12:27.430 --> 12:34.810
However, HPA can introduce hidden properties with either literal value or nested object form.

12:34.810 --> 12:43.210
More importantly, the Ruby is strongly typed, so the Math Assignment vulnerabilities cannot introduce new properties to the objects.

12:43.210 --> 12:52.210
However, the HPA can inject arbitrary properties, which make HPA really flexible and powerful.

12:53.710 --> 13:00.940
OK, so with several pages of concept introduction, I think it's time we can hack some real targets.

13:01.440 --> 13:08.340
In this example, we target at a popular web framework named Routing Controller.

13:08.360 --> 13:19.500
We will attack its official example code to demonstrate an end-to-end prototype inheritance hijack exploit, from security check bypassing to cycle injection.

13:20.340 --> 13:24.820
The figure on the right can give you a brief idea on how our example works.

13:24.820 --> 13:30.060
In the example, a server program is deployed using Routing Controllers.

13:30.680 --> 13:37.300
If a remote user wants to authenticate with the server, his data will flow into the following components.

13:37.420 --> 13:43.140
First, he will send his serialized data into the authentication module.

13:43.220 --> 13:52.280
Then, the authentication module will instantiate the objects according to the JSON he provides and send it to the param handler.

13:52.280 --> 13:57.080
Here, we use the green box to demonstrate the user data objects.

13:57.080 --> 14:03.320
The param handler is responsible for ensuring user input object is not malicious.

14:03.620 --> 14:18.320
The handler will first collect internal format specification object, which is the blue box in the picture, and he will merge the specification with the input object and invoke the input validation API.

14:18.320 --> 14:25.220
The input validation API will sanitize the user input data according to the format specification.

14:25.300 --> 14:30.020
In this case, it will check if the email field is legitimate or not.

14:30.020 --> 14:35.320
If the check passes, the user object will flow into the database.

14:35.600 --> 14:38.100
Okay, so this is the overall data flow.

14:38.100 --> 14:42.140
Let's analyze how we can attack the logic step by step.

14:43.000 --> 14:57.320
So, the first step is the hidden property injection, in which the malicious attacker injects hidden properties in his request, which is the constructor in this case.

14:57.320 --> 15:13.580
As shown in the picture, when the server program instantiates the user object, there will be an additional property named the constructor, which is a payload for bypassing the input validation module.

15:13.580 --> 15:20.820
So, in the second step, the program will prepare the parameters needed by the input validation API.

15:20.820 --> 15:29.280
The server program will merge the user input, which is the param, with an object named the schema.

15:29.280 --> 15:36.900
The merging operation is carried out by putting every property from the param objects into the schema object.

15:36.900 --> 15:41.280
So, this process is very much like object assignment.

15:41.280 --> 15:47.280
To simplify the demonstration, let's just use object assignment in this example.

15:47.360 --> 15:55.060
By performing such merging operations, the hidden property constructor will also transform to the schema.

15:55.540 --> 16:02.440
And after the transformation, we now can hijack the inheritance of the constructor on the schema.

16:02.440 --> 16:09.460
Actually, the constructor of the schema plays a very important role in the input validation module.

16:09.460 --> 16:19.120
As shown in the picture, the constructor actually stores the important format restriction information.

16:19.120 --> 16:27.900
As a result, the merging operation enables us to hijack the inheritance of these important format restrictions.

16:27.900 --> 16:38.860
As shown in the picture, when the constructor is read by the getSchema function, our hidden property will be immediately matched and returned to the code.

16:38.860 --> 16:49.700
To bypass the input validation, we just need to set the format specification as an invalid value, so that our SQL injection payload can escape the check.

16:50.020 --> 16:53.740
Now, the last step is much more straightforward.

16:53.740 --> 16:59.600
The validate payload then flows into the sensitive API to finish the entire attack.

16:59.720 --> 17:03.960
So, this is how an entire HPA chain looks like.

17:03.960 --> 17:09.040
Actually, the code logics behind vulnerability is much more complex.

17:09.040 --> 17:17.320
For example, the input validation module contains 30,000 lines of code.

17:17.320 --> 17:27.980
So, it would be helpful if we can find a tool that automatically track these data structures and discover time for hidden properties exist in the program logics.

17:28.700 --> 17:31.920
So, what are the challenges of building such a tool?

17:31.920 --> 17:34.060
First of all, it is JavaScript.

17:34.060 --> 17:38.180
Analyzing JavaScript is known to be hard due to the dynamic nature.

17:39.160 --> 17:46.320
And second, HPA is a tech that creates unexpected and new data dependencies.

17:46.360 --> 17:56.080
However, program analysis, such as data flow tracking, is mainly for analyzing the existing flows.

17:56.080 --> 18:02.340
Third, from our run examples, we can observe that HPA tampers internal program states.

18:02.760 --> 18:08.260
So, the attack effects highly depend on the roles of the compromised states.

18:08.260 --> 18:11.020
This makes the detection more challenging.

18:11.440 --> 18:22.420
To overcome these challenges, we design and implement Lynx, a hybrid JavaScript program analysis tool, to detect and exploit HPA vulnerabilities.

18:22.700 --> 18:25.300
Lynx mainly consists of two components.

18:26.160 --> 18:31.980
The first component on the left is for identifying potential hidden properties.

18:31.980 --> 18:43.440
It combines dynamic data flow tracking and static synthetics analysis to track all the user input and infer potential candidates.

18:43.440 --> 18:50.400
And the component on the right is for detecting the harmful hidden properties and generating exploits for them.

18:50.400 --> 19:01.360
To help future Node.js security research, we decided to open-source our Lynx project at the GitHub links in the bottom.

19:02.100 --> 19:08.920
So, if you are interested in the technical details, you can check it in the GitHub report.

19:09.760 --> 19:14.660
So, the very first thing Lynx will do is dynamic data flow tracking.

19:14.660 --> 19:22.520
First of all, Lynx will generate a label object, which is a unique key and value piv.

19:22.980 --> 19:27.880
Lynx will inject the label into the input data of the program.

19:28.300 --> 19:38.040
Since different properties from the input object may flow into different program logics, we want to track all these propagations.

19:38.040 --> 19:43.820
So, we perform the label injection in a recursive manner.

19:43.900 --> 19:53.640
That is, as shown in the picture on the left, Lynx will generate three different inputs by label injecting the original test case.

19:53.640 --> 19:57.980
In each time, Lynx will inject the label into our different properties.

19:58.740 --> 20:03.280
After the injection, Lynx will observe the program execution.

20:03.280 --> 20:10.380
We leverage a JavaScript analysis framework called Jalanki to instrument our test programs.

20:10.380 --> 20:18.500
Since we are studying the data flow, we instrument the variable reads and writes, object property indexing and function calls.

20:18.500 --> 20:21.060
Then, we execute the test program.

20:21.060 --> 20:26.040
During the execution, Lynx will examine every object within the data flow.

20:26.040 --> 20:32.520
If an object can't carry our property labels, we will record it for further analysis.

20:33.440 --> 20:37.380
So now we have a list of property carriers.

20:37.580 --> 20:46.080
Record that an object is flagged as a property carrier because we detect our injected label under his body.

20:46.080 --> 20:56.420
So if we can propagate our label here, maybe we can also propagate another malicious property here also.

20:56.420 --> 21:09.360
More specifically, if we can inject a property that has a conflicting name with certain internal properties the program has, maybe we can control that property by overwriting them.

21:10.840 --> 21:22.340
So now we want to extract all the child properties from the original programs of the property carriers and flag them as hidden property candidates.

21:22.340 --> 21:32.040
To achieve this goal, we need static syntax analysis to extract the necessary syntactic information from the code.

21:32.040 --> 21:37.460
The picture on the right demonstrates how we parse our statement from our running example.

21:37.460 --> 21:45.500
Lynx will traverse the syntax tree until reaching a property carrier, which is circled by the red line in the graph.

21:45.500 --> 21:49.520
Then it records all the properties under the carrier.

21:49.520 --> 21:53.800
In our case, the hidden property candidate is the constructor.

21:55.780 --> 22:01.140
So here is an output screenshot of the first component.

22:01.140 --> 22:10.800
As you can observe, the Lynx will first instrument the code base, and then we tracked 43 property carriers.

22:10.800 --> 22:17.940
As indicated by the red circle, Lynx successfully detects a hidden property named constructor.

22:19.520 --> 22:25.240
So in the previous component, we discovered the key name of the potential hidden properties.

22:25.240 --> 22:30.440
By injecting the property with the same key, we might overwrite certain internal states.

22:30.440 --> 22:36.240
However, we still don't know whether the candidates can be controlled or not.

22:36.240 --> 22:41.200
And we also don't know how to introduce attack effects with these candidates.

22:41.220 --> 22:44.340
So apparently, Lynx could do more.

22:46.060 --> 22:54.260
Let's revisit our running example to see if there is any insights to help us to design such an exploitation component.

22:54.340 --> 22:58.980
The figure on the left is the vulnerable code from our running example.

22:58.980 --> 23:09.960
As we have discussed many times, hidden property tampers internal program states, which means HPA exploitation highly relates it to the code context.

23:10.060 --> 23:15.400
So it is important to conclude a set of vulnerable sensitive behaviors.

23:15.400 --> 23:24.880
These behaviors should clearly indicate certain security consequences, so that we can decouple the harmfulness detection from the code context.

23:24.880 --> 23:31.760
Also, from the running example, the exploitation is mainly about manipulating the return result.

23:32.540 --> 23:36.660
More specifically, there are two possible paths here.

23:36.660 --> 23:43.400
If the execution enters the branch on line 19, we will get a validation failed.

23:43.400 --> 23:49.100
But if we can go into the line 21, we can successfully pass the check.

23:49.100 --> 24:00.100
So the exploitation point and the override point may not be the same place, which means we shouldn't stop our analysis at line 11.

24:00.100 --> 24:07.440
Instead, we should continue exploring all the possible paths that can be triggered by manipulating the hidden properties.

24:08.420 --> 24:13.580
So we studied and concluded six general types of sensitive things.

24:14.560 --> 24:19.960
Due to the time constraint, I will not introduce the details of each type.

24:19.960 --> 24:23.600
If you want to know more, you can check our Git report.

24:24.960 --> 24:36.420
So after defining our sensitive things, we want the hidden property to trigger as many as possible branches, and monitor whether we can hit a sink.

24:36.420 --> 24:42.480
To achieve this goal, we use symbolic execution to explore all the hidden property value space.

24:42.480 --> 24:49.920
Lynx first generates an exploit template that can reach the potential hidden vulnerable property.

24:50.080 --> 25:01.180
We denote such data structure as an exploit template, because Lynx does not specify a concrete value in the input.

25:01.180 --> 25:08.840
Instead, we insert a special placeholder, which will be used by the symbolic execution later on.

25:08.840 --> 25:18.020
Then, we run the test program with our constructed templates, and symbolically execute the hidden property.

25:18.020 --> 25:43.860
As shown in the picture, the Lynx will explore all the path constraints along the input path, and if Lynx finds that it has hit a sink, for example, in this example, it hit the sink i2, then he will fetch the corresponding payload that can trigger the sink as the final exploit.

25:44.240 --> 25:55.540
So a little background about the sink i2 is the sink we define to detect that whether our input can manipulate the return value of a module or not.

25:57.990 --> 26:01.610
So this is the output of the exploit module.

26:01.610 --> 26:09.790
From the circled area, we can observe that the key value pair constructN1 triggered the sink i2.

26:09.870 --> 26:19.270
In the last line, we can find that the Lynx successfully bypassed the generator exploit that can lead to the successful validation.

26:19.710 --> 26:23.010
So this is pretty much about how our system works.

26:23.010 --> 26:26.850
Let's see some interesting new results of our research.

26:26.850 --> 26:33.150
During our research, we choose 60 widely used programs from Node NPM.

26:33.150 --> 26:36.690
There are 55 modules and 5 web applications.

26:36.990 --> 26:49.330
And with the help of Lynx, we tracked more than 1,300 carriers, and detected more than 300 hidden property candidates associated with those carriers.

26:49.330 --> 26:53.130
In the end, we confirmed 13 zero-day vulnerabilities.

26:53.130 --> 26:59.510
With the help of symbolic execution, Lynx even synthesized 10 exploits automatically.

27:00.310 --> 27:03.630
So how is the impact of these vulnerabilities?

27:03.670 --> 27:15.030
We found that HPA can introduce various attack effects, such as leaking credential data, denial of services, or bypassing security checks.

27:15.030 --> 27:26.670
Based on the impact analysis, we can observe that the HPA can compromise previously unreachable program states, which effectively enlarge the attack surface.

27:26.850 --> 27:36.730
Even more, we could notice that HPA is not a simple input validation issue, and many input validators themselves are also vulnerable to HPA.

27:37.170 --> 27:45.230
So in the following slides, I will pick up two interesting vulnerabilities from our results and case study them.

27:45.910 --> 27:49.970
So the first case comes from MongoDB official driver.

27:49.970 --> 27:54.930
We found that we can tamper an internal state named the BSON type.

27:55.090 --> 28:05.270
Our background here is that the MongoDB leveraged the internal state BSON type to indicate the data type of the query object.

28:05.270 --> 28:13.190
However, when serializing the query object, MongoDB will ignore the object with unknown BSON type.

28:13.330 --> 28:18.270
So what if we abuse the logic for a query condition object?

28:18.270 --> 28:22.530
The code on the right is from an open source online game.

28:22.530 --> 28:27.610
The online game used a vulnerable API to implement the user management logics.

28:27.650 --> 28:42.730
As shown in the picture, by injecting the unknown BSON type to the input, the attacker can force the MongoDB not serializing the query object condition, so that the MongoDB will always return the first user on top of the database.

28:42.730 --> 28:51.190
With this ability, the attacker can log in or delete arbitrary accounts.

28:52.590 --> 28:57.250
The second case is from another widely used in-memory database.

28:57.250 --> 29:02.990
The hidden property is more like a backdoor which helps the user accessing the sensitive data.

29:02.990 --> 29:14.010
In TafiDB, we discover a hidden property named id, which is an internal index for each database data item.

29:14.010 --> 29:24.370
Once we specify our own id in the query, TafiDB will ignore other query conditions and directly return the result associated with the index.

29:24.370 --> 29:37.670
As shown in the picture, even though we got wrong password and username, we can still leak the valid user data from the database with our crafted hidden properties.

29:38.450 --> 29:40.690
Thanks for attending our talk.

29:40.690 --> 29:43.430
I hope you guys keep safe in this special year.

