Subversion (SVN)
A Subversion (SVN) repository has been discovered within the target web server, specifically located at the /svn/dev/
directory. Initially, access to the /svn
endpoint was restricted by Basic HTTP authentication. Upon attempting authentication using the default credential (admin:admin
), the server responded with a 403 Forbidden
status code. This initially suggested that further exploration might be futile.
However, through directory fuzzing, it was revealed that the /svn/dev/
subdirectory exists within the /svn/
path. This discovery indicates that despite the initial restriction, deeper reconnaissance can uncover additional accessible resources within the SVN repository.
info
┌──(kali㉿kali)-[~/PEN-200/PG_PRACTICE/phobos/svn]
└─$ svn info http://admin@$IP:80/svn/dev
Path: dev
URL: http://admin@192.168.104.131/svn/dev
Relative URL: ^/
Repository Root: http://admin@192.168.104.131/svn/dev
Repository UUID: 48a68f27-ba40-4519-a39f-bccbce31f2f4
Revision: 3
Node Kind: directory
Last Changed Author: admin
Last Changed Rev: 3
Last Changed Date: 2021-01-26 16:26:06 +0100 (Tue, 26 Jan 2021)
The current repository has been “revisioned” 5 times
ls
┌──(kali㉿kali)-[~/PEN-200/PG_PRACTICE/phobos/svn]
└─$ svn ls http://admin@$IP:80/svn/dev
internal/
manage.py
static/
submissions/
users/
As seen in the web server, there exist what appears to be source code of a Python web application. Those also correspond to the mapping results from ffuf earlier
log
┌──(kali㉿kali)-[~/PEN-200/PG_PRACTICE/phobos/svn]
└─$ svn log http://admin@$IP:80/svn/dev
------------------------------------------------------------------------
r3 | admin | 2021-01-26 16:26:06 +0100 (Tue, 26 Jan 2021) | 1 line
------------------------------------------------------------------------
r2 | admin | 2021-01-26 16:25:43 +0100 (Tue, 26 Jan 2021) | 1 line
Commit 2
------------------------------------------------------------------------
r1 | admin | 2021-01-26 16:25:37 +0100 (Tue, 26 Jan 2021) | 1 line
Created repository
------------------------------------------------------------------------
Not much information is provided to the commit log
Reversion
Reverting the current repository to older revisions is easily accomplished using the up
command in svn. This command efficiently navigates through the repository’s history, allowing users to explore and extract specific versions as needed
r3
┌──(kali㉿kali)-[~/PEN-200/PG_PRACTICE/phobos/svn]
└─$ svn checkout http://admin@$IP:80/svn/dev
[...REDACTED...]
Checked out revision 3.
In order to use the up
command in svn, the repository must be locally available.
For that, I will download the current repository to Kali using the checkout
command
┌──(kali㉿kali)-[~/…/PG_PRACTICE/phobos/svn/dev]
└─$ cd ./dev ; ll
total 32K
4.0K drwxrwxr-x 3 kali kali 4.0K Mar 9 16:01 ..
4.0K drwxrwxr-x 4 kali kali 4.0K Mar 9 16:00 users
4.0K drwxrwxr-x 7 kali kali 4.0K Mar 9 16:00 .
4.0K -rwxrwxr-x 1 kali kali 629 Mar 9 16:00 manage.py
4.0K drwxrwxr-x 2 kali kali 4.0K Mar 9 16:00 submissions
4.0K drwxrwxr-x 7 kali kali 4.0K Mar 9 16:00 static
4.0K drwxrwxr-x 2 kali kali 4.0K Mar 9 16:00 internal
4.0K drwxrwxr-x 4 kali kali 4.0K Mar 9 16:00 .svn
The repository has been downloaded to Kali
The notable aspect is the presence of the .svn
directory
Similar to the .git
directory in Git, the .svn
directory in Apache Subversion (SVN) serves as a crucial repository metadata storage. Understanding the contents of the .svn
directory is paramount, offering insights into repository structure and potentially exposing sensitive information.
manage.py
┌──(kali㉿kali)-[~/…/PG_PRACTICE/phobos/svn/dev]
└─$ cat manage.py
#!/usr/bin/env python3
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'internal.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()
It’s a Django app
users/views.py
┌──(kali㉿kali)-[~/…/PG_PRACTICE/phobos/svn/dev]
└─$ cat users/views.py
from django.shortcuts import render,redirect,HttpResponse
from django.contrib import messages
import subprocess
import string
import os,re
from django.contrib.auth.models import User
from django.contrib.admin.views.decorators import staff_member_required
from django.contrib.auth.decorators import login_required
from .forms import UserRegistrationForm,Passwordresetform
from django.contrib.auth import authenticate,logout
# Create your views here.
def register(request):
form=UserRegistrationForm()
context={'form':form}
if(request.method=="POST"):
try:
form=UserRegistrationForm(request.POST)
if(form.is_valid):
form.save()
return redirect('login')
except ValueError:
messages.error(request,'Pls check your input fields')
return redirect('register')
return render(request,template_name='registration.html',context=context)
@login_required
def home(request):
return render(request,template_name='home.html')
@login_required
def account(request):
form=Passwordresetform()
context={'form':form}
if(request.method=="POST"):
form=Passwordresetform(request.POST)
if(form.is_valid()):
print(form.cleaned_data.get('username'))
user=User.objects.get(username=request.POST['username'])
if(form.cleaned_data.get('newpass')==form.cleaned_data.get('cnewpass')):
user.set_password(form.cleaned_data.get('newpass'))
user.save()
messages.success(request,message='Password successfully changed')
return redirect('home')
return render(request,template_name='account.html',context=context)
@login_required
def jobs(request):
if request.method=='POST':
messages.success(request,message='Your work have been submitted to admin he will review it so please wait')
return redirect('home')
return render(request,template_name='jobs.html')
@staff_member_required
def remove_view_submissions(request):
if(request.method=="POST"):
action=request.POST["action"]
if(action=="view"):
f=request.POST["file"]
fil=open('/var/www/html/internal/submissions/'+f,'r')
print(f)
output=fil.read()
return HttpResponse(content=output)
elif(action=="delete"):
cmd=["rm","/var/www/html/internal/submissions/{}".format(request.POST["file"])]
cmd="/bin/bash -c 'rm /var/www/html/internal/submissions/{}'".format(request.POST["file"])
print(cmd)
a=os.system(cmd)
messages.info(request,message="The file has been deleted")
files=subprocess.Popen(['ls','/var/www/html/internal/submissions'],stdout=subprocess.PIPE).stdout.read().decode().split('\n')
print(files)
context={"files":files}
return render(request,template_name='submissions.html',context=context)
The users/views.py
file shows the following endpoints:
/register
/login
/home
/account
/jobs
/submissions
Broken File Upload
The file upload feature is not implemented properly
Vulnerability (Password Reset)
While it would appear that the web application supports file upload and uploaded files gets executed, there is a flaw in the
account
function
It does not check for old password. This would mean anybody can reset the password of any existing user
Vulnerability (Code Execution)
There is a OS command injection vulnerability present if the
delete
value is supplied to the action
parameter. The user is able to control the file
parameter, leading to OS command injection.
internal/settings.py
┌──(kali㉿kali)-[~/…/PG_PRACTICE/phobos/svn/dev]
└─$ cat internal/settings.py | grep -v '^#'
"""
Django settings for internal project.
Generated by 'django-admin startproject' using Django 2.2.16.
For more information on this file, see
https://docs.djangoproject.com/en/2.2/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/2.2/ref/settings/
"""
import os
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
SECRET_KEY = 'w7hj11z7mg+cotad702-+vz)gd==xz=8zd=k4=eq8ir2@36%*('
DEBUG = False
ALLOWED_HOSTS = ['*']
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'users',
'widget_tweaks'
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'internal.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'internal.wsgi.application'
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
LOGIN_URL='login'
LOGIN_REDIRECT_URL = 'home'
The internal/settings.py
file reveals the app key; w7hj11z7mg+cotad702-+vz)gd==xz=8zd=k4=eq8ir2@36%*(
The backend DB is sqlite3
internal/urls.py
┌──(kali㉿kali)-[~/…/PG_PRACTICE/phobos/svn/dev]
└─$ cat internal/urls.py
"""internal URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/2.2/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path
from django.contrib.auth.views import LogoutView,LoginView
from users.views import register,home,account,jobs,remove_view_submissions
urlpatterns = [
path('',LoginView.as_view(template_name='login.html')),
path('home/',home,name='home'),
path('login/',LoginView.as_view(template_name='login.html'),name='login'),
path('logout/',LogoutView.as_view(template_name='logout.html'),name='logout'),
path('register/',register,name='register'),
path('account/',account,name='account'),
path('jobs',jobs,name='jobs'),
path('submissions/',remove_view_submissions,name="submission")
]
#path('admin/', admin.site.urls)
The internal/urls.py
file also reveals the routing
There is also a comment that suggesting that there might be an admin page; admin/
r2
┌──(kali㉿kali)-[~/…/PG_PRACTICE/phobos/svn/dev]
└─$ svn up -r 2
Updating '.':
A todo
Updated to revision 2.
Reverting to the 2nd commit shows that the todo
file was appended
todo
┌──(kali㉿kali)-[~/…/PG_PRACTICE/phobos/svn/dev]
└─$ cat todo
*Change this application to a this virtual host internal-phobos.phobos.offsec
*Randomise the secret key
* Make a database for maintaining employee ssh credentials
* Move the entire site to a docker container
* Configure the ufw firewall
The todo
file reveals a hidden virtual host for an internal application; internal-phobos.phobos.offsec
- There is also a mention of making a database for SSH credentials
- The application might be running inside a Docker container
- The target environment has firewall enabled
The virtual host has been appended to the
/etc/hosts
file on Kali for local DNS resolution
r1
┌──(kali㉿kali)-[~/…/PG_PRACTICE/phobos/svn/dev]
└─$ svn up -r 1
Updating '.':
D todo
Updated to revision 1.
Switching to the first commit deletes the todo
file