Beginner Linux CTF Walk-through

Reading time ~22 minutes


This post is a complete walk-through of the Linux CTF by OffSec club at Dakota State University.

Things to Note

  • Read the Disclaimer before reading this post.
  • This post assumes that you know a little bit about linux and to use basic commands and some basic programming skills.


Linux OffSec Club at Dakota State University has setup a beginner Linux CTF challenge at Initially you are given the credentials to a Level-0(user1) user (check out the website for the creds), then you login via SSH and start looking around for the password of the next user(user2) and this repeats until user15. Your goal is to reach the end. Alright, let’s start with Level-0.


Visit the url, you’ll see the credentials for the user1. Use these credentials to login via SSH. If you are on Windows you can use putty and if you are on Linux or Mac, you can simply use the ssh command.


  • Open Putty.exe
  • Enter as the host.
  • Click open
  • Enter username & password
  • That’s it!


  • Open Terminal
  • Type
  • Enter the password for the user1
  • That’s it!


  • we use ssh command to login to a remote machine to get a shell.
  • Command ssh user@domain.



Now that you have a remote shell via SSH, you need to start looking for the password of the next user i.e., user2. First thing that we can do is check where we are in the filesystem.

user1@webserver:~$ pwd

As you can see, we are in user1’s home directory. Let’s try listing all the files in the current directory.

user1@webserver:~$ ls

We see nothing, but If you know a bit about ls, you’d know that ls lists all the files except the hidden ones. To view hidden files we can use -a

user1@webserver:~$ ls -a
.  ..  .bash_history  .bash_logout  .bashrc  .here  .profile  .ssh

Okay, there’s a .here hidden folder, We can take a look at what’s inside.

user1@webserver:~$ cd .here
user1@webserver:~$ cat pass

There we go, the file pass had the password to our next user(user2).


  • Hidden files start with a .(dot).



Now we can use the same command from Level-0 and log into user2 with the password CentsMaybeCarry55. Remember that I won’t be showing you how to connect via SSH with a password in every level, it is assumed that you know how to connect via SSH after reading the Level-0 solution.

Listing all the files in the current directory, we get

user2@webserver:~$ ls

We can see the password file there let’s try opening it

user2@webserver:~$ cat $!\Password&
[1] 14794
user2@webserver:~$ cat: Password: No such file or directory

[1]+  Exit 1                  cat $!\Password

What just happened? If you look at the filename, you’ll see that it has some special characters like $. This means that we need to escape these characters in order to access the file. We can escape these characters using a backslash(\).

user2@webserver:~$ cat \$\!\\Password\&

There we go, WereElseLabor80 is our password, but we can solve this challenge in another easy way and this is cheating in a way, but hey! they never mentioned any rules, did they? Anyways we can use the wildcard(*) which means everything, so cat reads all files in the current directory.

user2@webserver:~$ cat *


  • Use backslashes(\) to escape special characters.
  • * means wildcard and it matches everything.



Listing all the files in the home directory of user3, we get,

user3@webserver:~$ ls

Let’s read what’s inside,

user3@webserver:~$ cat password 

Whoaa what’s this? This is an RSA Private Key(see resources), If you didn’t know, we can use a private key instead of a password to authenticate to an SSH server. It’s more secure this way. The public key is stored on the SSH server, so whenever you try to connect with an SSH private key, the SSH server verifies the key pair and logs you in.

So here’s what we gonna to do, we’ll copy the contents of the password file to another file ssh-key.txt on our machine, not the CTF one. Then we can simply use -i option to specify the private key.

you@your-machine:~$ chmod 600 ssh-key.txt
you@your-machine:~$ ssh -i ssh-key.txt

We do the chmod 600 ssh-key.txt because ssh will throw us with some errors(Insecure SSH key file permissions).

That’s it!

If you are using putty, you might wanna look at this


  • You can connect to the SSH server with a private key provided that the public key is on the server.



Now, let’s do a complete listing of the files

user4@webserver:~$ ls -a
.  ..  .bash_history  .bash_logout  .bashrc  .git  .inputrc  .profile  .ssh

Ooooooo whats .git? Well,

” Git is a free and open source distributed version control system designed to handle everything from small to very large projects with speed and efficiency. “

Alrighty, let’s try to view the logs

user4@webserver:~$ git log
fatal: Not a git repository (or any of the parent directories): .git

What happened? Well, apparently the git folder is corrupted or its just a dummy. Usually, in a typical CTF git challenge, the flag.txt or password or any proof-of-work string/file is commited to the git repo, but later down the road, the file will be deleted. But since git is a version control system, it keeps history of all the commits, you can simply revert back to that commit and extract the password/flag.txt. But in this case, it was not related to commits and history. It’s probably something different, let’s look inside the .git directory

user4@webserver:~$ cd .git
user4@webserver:~/.git$ ls -a
.  ..  .nolook

Let’s check out .nolook

user4@webserver:~/.git$ cd .nolook
user4@webserver:~/.git/.nolook$ ls -a
!:{g5Z  .  ..  .Bm@^[  .N~WSM  .l@<X`

Ohhkayyy, Let’s go inside with * wildcard(Refer Level-2)

user4@webserver:~/.git/.nolook$ cd *
user4@webserver:~/.git/.nolook/!:{g5Z$ ls -a 
.  ..  e^z|,>
user4@webserver:~/.git/.nolook/!:{g5Z$ cd *
user4@webserver:~/.git/.nolook/!:{g5Z/e^z|,>$ ls -a
.  ..  G_4)xg

You see where this is going? we get the brutal directory traversal challenge. But we can use grep to solve this challenge. grep with -R recursively checks for the regular expression. Here, . represents any character in regular expressions, so we recursively check for any character going inside the directory tree.

user4@webserver:~/.git/.nolook$ grep -R .

There we go, we see the password CloudGiftMalta49 at the end of the output. There are other ways of solving this challenge like cd */*/*/*/*/*/*/*/*... or doing something programmatically, but I think grep is the simple one.


  • grep -R can recursively go in a directory tree and search for specifics.
  • These kinds of tedious/brutal challenges are all about your programming skills or a program specific skill.
  • Never do it manually (maybe sometimes, but rarely).



Onto the next one.

user5@webserver:~$ ls
group.txt password.txt

Alright the password is right there

user5@webserver:~$ cat password.txt 
cat: password.txt: Permission denied

This time there’s something to do with the permissions. Let’s check out the other file group.txt

user5@webserver:~$ cat group.txt    
SG(1)                                                   User Commands                                                  SG(1)

       sg - execute command as different group ID

       sg [-] [group [-c ] command]

       The sg command works similar to newgrp but accepts a command. The command will be executed with the /bin/sh shell.
       With most shells you may run sg from, you need to enclose multi-word commands in quotes. Another difference between
       newgrp and sg is that some shells treat newgrp specially, replacing themselves with a new instance of a shell that
       newgrp creates. This doesn't happen with sg, so upon exit from a sg command you are returned to your previous group

       The following configuration variables in /etc/login.defs change the behavior of this tool:

       SYSLOG_SG_ENAB (boolean)
           Enable "syslog" logging of sg activity.

           User account information.

           Secure user account information.

           Group account information.

           Secure group account information.

       id(1), login(1), newgrp(1), su(1), gpasswd(1), group(5), gshadow(5).

shadow-utils 4.4                                         05/17/2017                                                    SG(1)

Hmm… This looks like a man page of sg command. Here’s the thing, the password.txt can be read by a specific group only and we are not that group, so that’s the reason we are not able to access it. So what’s with the sg command, you might’ve guessed it or you probably knew about this, this command helps us execute a command as a group that we want, given that you have the password for it.

user5@webserver:~$ ls -l
total 8
-r-xr-x--- 1 root challenges 1400 Feb 21 23:47 group.txt
-r-xr-x--- 1 root password     16 Feb 20 20:33 password.txt

As you can see, the group asscociated with the password.txt is password.

Let’s use the sg command and try reading this

user5@webserver:~$ sg password -c "cat password.txt"
Password: CloudGiftMalta49

Boom, there’s the password SoftBoneFound59.


  • You can use sg command to execute a command as a different group.



Listing all the files of user6’s home directory gives us

user6@webserver:~$ ls

Okay, let’s check it out

user6@webserver:~$ cat password.txt 
��֪�	����������� �����

Whaaaat the ffffffffile? Unprintable characters!? This could mean only one thing, it’s probably an executable or some sort of a specific file type that’s not really meant to be read directly.

user6@webserver:~$ file password.txt 
password.txt: data

Raw data, Interesting…

So what we need to do is, get the password out from this data somehow. Since we see all the unprintable characters, let’s check if there are any printable characters inside this data. We can filter that using the strings command

user6@webserver:~$ strings password.txt 

There we go, the password SaveThanGoes26 was hidden somewhere inside this huge junk of data.


  • Use file command to check what kind of file you’re dealing with.
  • Use string to extract printable characters.



Let’s see what we are dealing with this time

user7@webserver:~$ ls
user7@webserver:~$ cat password.txt 

Nope? what does Nope mean? It should either give us the contents of the file or give us an error message, not Nope, unless Nope is the content of this file which seems skeptical. There’s one other possiblity, The cat command (which is actually an executable file) resides in /usr/bin/cat or maybe someplace else which is quite common. Now let’s check where does cat exist in filesystem using which command

user7@webserver:~$ which cat

Ahaaa, there it is, it’s some kind of a broke version of cat and the PATH environment variable is overwritten by this new cat path location so that if you use cat command it executes a broke one instead of the original one. You can also check if the broke cat is really broke by

user7@webserver:~$ echo "Are you the real cat?" > /tmp/hahaha.txt
user7@webserver:~$ cat /tmp/hahaha.txt 

So lets see if the original cat exists in /usr/bin

user7@webserver:~$ ls /usr/bin/ | grep "cat"

Oh, it’s not here, let’s check /bin

user7@webserver:~$ ls /bin/ | grep "cat"

There you go, now let’s use this cat by specifying the entire path to it like

user7@webserver:~$ /bin/cat password.txt 

Alternatively you can use grep

user7@webserver:~$ grep "." password.txt 

So EachPeaceLand64 is our password. Sweeeeeeeeet :) Onto the next one.


  • Never trust something blindly, always test the theory.


What do we have this time?

user8@webserver:~$ ls
go here => cyberchef  password.txt

There’s a clear hint “go here => cyberchef”. CyberChef is a Cyber Swiss Army Knife tool for encoding/decoding and hash calculations. This tool is basically an html file, you can download it from the github repo or you can use the online version, I’ll use the online version.

go here =>

Now let’s read what’s inside password.txt

user8@webserver:~$ cat password.txt 

First line looks like Base64 encoding. You might recognize this, you might not, but remember if you see ASCII characters which mean nothing and = is being padded at the end, it might probably be a Base64 encoded string. The = is padded so that the smaller bits can round up to a whole. Base64 Also has a character set A-Za-z0-9+/= which translates to

  • A-Z
  • a-z
  • 0-9
  • +/=

These are the only characters used in Base64 Now we know that this is Base64, let’s decode this. We can do this in commandline, but let’s try out CyberChef because you’ll learn to use a new tool.

Follow these steps

  • Visit CyberChef
  • From the left panel, expand Data format
  • Drag From Base64 to the Recipe window
  • In the Input window Enter the first line of the password.txt i.e., V2Vhcg==
  • Click on BAKE! button, you’ll see the first part of the password in the output window.

Similarly we do it for Base32 and Base58, because

  • Base32 has the character set A-Z2-7= and K5XXK3DE matches this set.
  • Base58 has the character set 123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz and 3ro2uVYXCb matches this set.

So now, remove the From Base64 from the Recipe window by clicking on the delete button on the top right corner of the Recipe window.

Perform the same steps for Base32 (K5XXK3DE) and Base58 (3ro2uVYXCb) and you’ll end up with

  • From Base64(‘V2Vhcg==’) : Wear
  • From Base32(‘K5XXK3DE’) : Would
  • From Base58(‘3ro2uVYXCb’) : Known80

So the final password is WearWouldKnown80


  • Get yourself familiar with different encoding types and it will help you detecting them in the future.
  • CyberChef is a handy tool for encoding/decoding and calculating hashes, so learn to use it.



Alright, let’s see what do we have this time

user9@webserver:~$ ls
user9@webserver:~$ cat password.txt 

Okay, this time it looks like a hash. What are hashes? They are one-way encryptions, this means you cannot reverse em back or decrypt em. So how are we suppose to extract the password? We can convert a password into it’s hash form and check if the hash matches the hash in password.txt file. If it does, then that’s the password. Simple right? Not really, There are a lot of combinations from available characters on the keyboard

  • A-Z
  • a-z
  • 0-9
  • All symbols

When we calculate the combinations, it’s going to be a huge number, which takes a lot of time and computing power. Instead we can use an online Hash Lookup Table. These are basically stored calculations of words and hashes. We’ll use

  • Visit
  • Enter the hash found in the password.txt i.e, a560b1ca4d01f8ebfe89247ff7df6a88
  • Complete the Google captcha and click on Crack Hashes
  • We get a result
Hash Type Result
a560b1ca4d01f8ebfe89247ff7df6a88 md5 sharkbait

sharkbait is our password.


  • Learn to identify hashes.
  • If you can’t crack it, use online resources like crackstation for cracking fairly common/easy-to-crack passwords.



What do we have?

user10@webserver:~$ ls
password.txt  wordcloud.txt

let’s check out password.txt

user10@webserver:~$ cat password.txt 
the of and to a in for is on that by this with i you it not or be are from at as your all have new more an was we will home can us if page my has free but our one do no time they site he up may what news out use any see only so his when here who web also now help get pm view c e am been how were me s some its like x than find date back top had list name just over year day into two n re next used go b work last most buy data make them post her city t add such best then jan good well d info high m each she very book r read need many user said de does set mail full map life know way days p part real f item ebay must made off line did send type 

Looks like we have some random words, let’s check out wordcloud.txt for any clues.

user10@webserver:~$ cat wordcloud.txt 
in order

in order… That’s a useful hint, because we have words and we have to do a wordcloud, which is basically a nice visual way of representing different words based on their frequency of occurance. So if we find the frequencies of all the words and sort them by count, we’ll find the password. We can solve this in a number of ways, you can use an online tool like TagCrowd or write a python script. Let’s not use the online tool, because it’ll be too easy, let’s use python for calculating the frequency and picking the top 3. Follow the steps

  • Create a file called words.txt with all the words from password.txt.
  • Type python and hit enter
>>> from collections import Counter # Import Counter from collections module
>>> f = open('words.txt', 'r') # Open words.txt in read mode
>>> words = # Read words from the handle 'f'
>>> words = words.split(" ") # Split the string to a list by space
>>> counts = Counter(words) # Use counter for counting frequencies
>>> print(counts) # print the result
Counter({'angry': 10, 'major': 8, 'watch': 7, 'ciao': 4, 'hats': 4 ...

As you can see “angry”, “major” and “watch” has top 3 frequencies and from wordcloud.txt we know that they are “in order”.

So the password must be angrymajorwatch and it is.

You can also use online word frequency counter to extract top 3 and put em in order, you’ll have the password.


  • wordcloud = word frequency
  • Always use automation (Python or online or anything you like)



Reading the contents of password.txt gives us

user11@webserver:~$ ls
user11@webserver:~$ cat password.txt 

The Project Gutenberg EBook of Moby Dick; or The Whale, by Herman

This eBook is for the use of anyone anywhere at no cost and with almost
no restrictions whatsoever.  You may copy it, give it away or re-use it
under the terms of the Project Gutenberg License included with this
eBook or online at

We have some sort of a book. First time when I encountered this challenge, I thought this might be something to do with the book itself, so I researched online but ended up finding nothing. The real deal was to extract the password from the book using grep. Yes, it is that simple, but the position of this challenge as Level-11 in the CTF game will throw off people like me into a rabbit hole while this entire challenge was too easy.

user11@webserver:~$ cat password.txt | grep -i "password"
spirally coiled away in the tub, not Password: EarlyHersCase97 like the worm-pipe of a still

grep -i is for case insensitive matching and EarlyHersCase97 is our password.

Onto the next one.


  • Sometimes, the answer is right in front if your eyes and it’s simple, you try to ignore it by thinking too fast. Slow down and try everything. You’ll defintely figure it out. Time & Imagination are your only constraints.
  • grep -i is the case insensitive flag for regular expression matching.


cat-ing password.txt outputs

user12@webserver:~$ cat password.txt

It ends with ==(double equals - due to the font, it looks a bit wierd), If you remember from Level-8, this looks similar. YES, It’s Base64, but huge.

We can use base64 command in linux to extract the password. Let’s try that

user12@webserver:~$ cat password.txt | base64 -d 

Okayy, it’s again Base64, let’s decode it again

user12@webserver:~$ cat password.txt | base64 -d | base64 -d

I think we get it, It encoded a lot of times, so we need to decode it recursively. I’ll use python for this, you can do it with just shell scripting, but I love python. First thing to do is to copy the file to /tmp/whatever_directory because you are not allowed to write to files in home directory.

# Save it as
f = open('password.txt','r') # Open the password file to read
data = # store the base64 string we read from password.txt to data
f.close() # close the handle
for i in range(100): # a loop defined to runs 100 times
        data = (data.decode('base64')) # try to decode data and store it back to data
        print("End:", i) # if we couldn't decode, we print the iteration number
        f2 = open("new_password.txt", 'w') # Create another file to write
        f2.write(data) # store the data in a file so we don't loose it
        f2.close() # close the handle
user12@webserver:/tmp/ssx$ python 
('End:', 41)

We get 41, so maybe till 40, It was a valid Base64 string. Let’s check out the new file we generated new_password.txt.

user12@webserver:/tmp/ssx$ cat new_password.txt 

It’s not a normal file for sure, so let’s use file command

user12@webserver:/tmp/ssx$ file new_password.txt 
new_password.txt: gzip compressed data, was "password.txt", last modified: Wed Feb 21 23:24:40 2018, from Unix

Well, would you look at that? It’s a gzip compressed data. It’s an archive. So let’s uncompress this file. I created a new directory called new so that the extracted files will be seperate from the other ones like my python script. I also renamed new_password.txt to new_password.gz

user12@webserver:/tmp/ssx/new$ mv new_password.txt new_password.gz  
user12@webserver:/tmp/ssx/new$ gunzip new_password.gz 
user12@webserver:/tmp/ssx/new$ ls
user12@webserver:/tmp/ssx/new$ cat new_password 

QuickHourEver20 is our password.


  • Repetitive tasks are not meant for humans, use automation scripts, let the computer help you.
  • Always use file for confirming the file type, don’t be deceived by the file extensions.


Just 2 more levels, keep going!


Listing home directory gives us

user13@webserver:~$ ls
challenge  challenge.c  password.txt

Interesting …

lets do a file on each file

user13@webserver:~$ file *
challenge:    setuid ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/, for GNU/Linux 2.6.32, BuildID[sha1]=436d911632507a6dfe029f419cbc2d2e080566e6, not stripped
challenge.c:  C source, ASCII text
password.txt: regular file, no read permission
  • challenge is a 64-bit ELF executable
  • challenge.c looks like the source code of the executable in C
  • password.txt is a regular text file

If we try to access password.txt we get

user13@webserver:~$ cat password.txt 
cat: password.txt: Permission denied

so let’s check the permissions of all the files

user13@webserver:~$ ls -l
total 20
-r-sr-x--- 1 user14 challenges 8888 Feb 21 23:29 challenge
-r-xr-x--- 1 root   challenges  393 Feb 21 23:29 challenge.c
-r-xr-x--- 1 user14 root         16 Feb 21 23:21 password.txt

So only root group has the ablity to read password.txt, but challenge is interesting because it has a SETUID bit. That means it can read the password.txt for us.

let’s run that

user13@webserver:~$ ./challenge
Fail 4 != 1

After executing the program, it waited for some input I typed “ASD”, but it gave us a error message Fail 4 != 1. We might have to reverse engineer this executable, but there’s the challenge.c source code right there, so we can just look at it.

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {

    int c;
    char buff[1001] = {0};

    for(c=1; c<1000; c++) {

        fgets(buff, 1000, stdin);
        if(strlen(buff) != c) {
            printf("Fail %d != %d\n", strlen(buff), c);


    system("/bin/cat /home/user14/password.txt");

    return 0;


Basically this program loops for 1000 times. In every loop, it takes the input and stores it in a variable called buff and checks if the length of the input i.e., buff is same as the loop index c. If it’s not then it’ll give us the error.

Example case:

user13@webserver:~$ ./challenge

Fail 7 != 6

The table illustrates each loop from the above example

String Length Variable ‘c’ Statement State
“\n” 1 1 1 == 1 true
“1\n” 2 2 2 == 2 true
“12\n” 3 3 3 == 3 true
“555\n” 4 4 4 == 4 true
“3232\n” 5 5 5 == 5 true
“333333\n” 7 6 7 == 6 false

Since 7 != 6, we get the “Fail” message for the last case.

So the input length has to be same as the variable c. We can do this by hand like a thousand times. But it’s insane, one small mistake, you are done, you have to start all over again. So we write a simple python one liner.

user13@webserver:~$ python -c "for i in range(10): print '1'*i" 


We can use -c option to write python code like the above, making it a single liner. All it does is that it prints number ‘1’ in increasing order like the program challenge needs. Now we just have to change the number 10 to 1000 and pipe it to the challenge program.

user13@webserver:~$ python -c "for i in range(1000): print '1'*i" | ./challenge

There it is, BookVerbLeast61 is our password. Sweeeeeet :)


  • Repetitive tasks = computer’s job
  • Understand the source of a program completely before trying to attack it.



What do we get now?

user14@webserver:~$ ls
challenge  challenge.c  password.txt

Same? ofcourse not, let’s look at challenge.c, because we know what’s up with the other 2 files(password.txt is not readable by us, but since challenge has a setuid bit, it can read the password but we need to complete the challenge).

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {

        int c, tmp;
        char password[] = "YouCantGetIt";

        for(c=0; c<strlen(password); c+=4) {
                scanf("%d", &tmp);
                if(tmp != *(int *)&password[c])

        system("/bin/cat /home/user15/password.txt");


We can see the password right there, but there’s more to it. Basically this checks if the entered input is equal to the value of (int *) of the character in the password char array and also the variable c increments by 4 that means it loops 3 times.

All we gonna do is copy the source code, modify it a bit and make it spit out the right values. The final source is down below, Instead of stepping through the debugger to find out the values, we can just simply remove the if statement with the scanf and simply print the value of *(int *)&password[c] in each iteration.

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
    int c, tmp;
    char password[] = "YouCantGetIt";
    for(c=0; c<strlen(password); c+=4) {
            printf("%d\n", *(int *)&password[c]);

Loop trace:

Loop c password[c] *(int *)&password[c]
1 0 ‘Y’ 1131769689
2 4 ‘a’ 1198812769
3 8 ‘e’ 1950970981

Now we can run the program challenge and simply enter these values

user14@webserver:~$ ./challenge

ClassPlacePress16 is our password.


  • If the challenge is hardcoded, modify the source and make it spit results for you.



You should be able to log into the last user, that’s user15.

That’s it!

We’ve completed the CTF, we are now the user15. The “last user” right? or is it not?

That’s all for now folks. Thank you for reading. Have a great day :)

– s0cket7

Pico CTF 2018 Web Exploitation Writeup

A writeup of all 18 Web Challenges from PicoCTF Continue reading

IDOR leads to account takeover

Published on August 16, 2018

Open Redirect Vulnerability

Published on August 15, 2018