Skip to content

Commit 06df365

Browse files
Merge pull request #471 from AlexAQ972/socks5
Add support for socks5
2 parents 00852fd + eb7e37b commit 06df365

File tree

8 files changed

+371
-0
lines changed

8 files changed

+371
-0
lines changed
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
internal 0.0.0.0
2+
external 0.0.0.0
3+
4+
maxconn 10
5+
6+
auth none
7+
8+
socks -p1080
9+
10+
allow *
11+
12+
flush
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#!/usr/bin/env bash
2+
3+
set +e
4+
5+
echo "socks5/cleanup: Tests cleanup for socks5"
6+
7+
CONTAINER_NAME=zgrab_socks5
8+
9+
docker stop $CONTAINER_NAME

integration_tests/socks5/setup.sh

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#!/usr/bin/env bash
2+
3+
echo "socks5/setup: Tests setup for socks5"
4+
5+
CONTAINER_TAG="3proxy/3proxy"
6+
CONTAINER_NAME="zgrab_socks5"
7+
8+
# If the container is already running, use it.
9+
if docker ps --filter "name=$CONTAINER_NAME" | grep -q $CONTAINER_NAME; then
10+
echo "socks5/setup: Container $CONTAINER_NAME already running -- nothing to setup"
11+
exit 0
12+
fi
13+
14+
DOCKER_RUN_FLAGS="--rm --name $CONTAINER_NAME -e "PROXY_USER=user" -e "PROXY_PASS=password" -v ./3proxy.cfg:/etc/3proxy/3proxy.cfg -td"
15+
16+
# If it is not running, try launching it -- on success, use that.
17+
echo "socks5/setup: Trying to launch $CONTAINER_NAME..."
18+
if ! docker run $DOCKER_RUN_FLAGS $CONTAINER_TAG; then
19+
echo "failed"
20+
# echo "socks5/setup: Building docker image $CONTAINER_TAG..."
21+
# # If it fails, build it from ./container/Dockerfile
22+
# docker build -t $CONTAINER_TAG ./container
23+
# # Try again
24+
# echo "socks5/setup: Launching $CONTAINER_NAME..."
25+
# docker run $DOCKER_RUN_FLAGS $CONTAINER_TAG
26+
fi

integration_tests/socks5/test.sh

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#!/usr/bin/env bash
2+
3+
set -e
4+
MODULE_DIR=$(dirname $0)
5+
ZGRAB_ROOT=$(git rev-parse --show-toplevel)
6+
ZGRAB_OUTPUT=$ZGRAB_ROOT/zgrab-output
7+
8+
mkdir -p $ZGRAB_OUTPUT/socks5
9+
10+
CONTAINER_NAME=zgrab_socks5
11+
12+
OUTPUT_FILE=$ZGRAB_OUTPUT/socks5/socks5.json
13+
14+
echo "socks5/test: Tests runner for socks5"
15+
# TODO FIXME: Add any necessary flags or additional tests
16+
CONTAINER_NAME=$CONTAINER_NAME $ZGRAB_ROOT/docker-runner/docker-run.sh socks5 > $OUTPUT_FILE
17+
18+
# Dump the docker logs
19+
echo "socks5/test: BEGIN docker logs from $CONTAINER_NAME [{("
20+
docker logs --tail all $CONTAINER_NAME
21+
echo ")}] END docker logs from $CONTAINER_NAME"
22+
23+
# TODO: If there are any other relevant log files, dump those to stdout here.

modules/socks5.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package modules
2+
3+
import "github.com/zmap/zgrab2/modules/socks5"
4+
5+
func init() {
6+
socks5.RegisterModule()
7+
}

modules/socks5/scanner.go

Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
// Package socks5 contains the zgrab2 Module implementation for SOCKS5.
2+
package socks5
3+
4+
import (
5+
"fmt"
6+
"net"
7+
8+
log "github.com/sirupsen/logrus"
9+
"github.com/zmap/zgrab2"
10+
)
11+
12+
// ScanResults is the output of the scan.
13+
type ScanResults struct {
14+
Version string `json:"version,omitempty"`
15+
MethodSelection string `json:"method_selection,omitempty"`
16+
ConnectionResponse string `json:"connection_response,omitempty"`
17+
ConnectionResponseExplanation map[string]string `json:"connection_response_explanation,omitempty"`
18+
}
19+
20+
// Flags are the SOCKS5-specific command-line flags.
21+
type Flags struct {
22+
zgrab2.BaseFlags
23+
Verbose bool `long:"verbose" description:"More verbose logging, include debug fields in the scan results"`
24+
}
25+
26+
// Module implements the zgrab2.Module interface.
27+
type Module struct {
28+
}
29+
30+
// Scanner implements the zgrab2.Scanner interface, and holds the state
31+
// for a single scan.
32+
type Scanner struct {
33+
config *Flags
34+
}
35+
36+
// Connection holds the state for a single connection to the SOCKS5 server.
37+
type Connection struct {
38+
buffer [10000]byte
39+
config *Flags
40+
results ScanResults
41+
conn net.Conn
42+
}
43+
44+
// RegisterModule registers the socks5 zgrab2 module.
45+
func RegisterModule() {
46+
var module Module
47+
_, err := zgrab2.AddCommand("socks5", "SOCKS5", module.Description(), 1080, &module)
48+
if err != nil {
49+
log.Fatal(err)
50+
}
51+
}
52+
53+
// NewFlags returns the default flags object to be filled in with the
54+
// command-line arguments.
55+
func (m *Module) NewFlags() interface{} {
56+
return new(Flags)
57+
}
58+
59+
// NewScanner returns a new Scanner instance.
60+
func (m *Module) NewScanner() zgrab2.Scanner {
61+
return new(Scanner)
62+
}
63+
64+
// Description returns an overview of this module.
65+
func (m *Module) Description() string {
66+
return "Perform a SOCKS5 scan"
67+
}
68+
69+
// Validate flags
70+
func (f *Flags) Validate(args []string) (err error) {
71+
return
72+
}
73+
74+
// Help returns this module's help string.
75+
func (f *Flags) Help() string {
76+
return ""
77+
}
78+
79+
// Protocol returns the protocol identifier for the scanner.
80+
func (s *Scanner) Protocol() string {
81+
return "socks5"
82+
}
83+
84+
// Init initializes the Scanner instance with the flags from the command line.
85+
func (s *Scanner) Init(flags zgrab2.ScanFlags) error {
86+
f, _ := flags.(*Flags)
87+
s.config = f
88+
return nil
89+
}
90+
91+
// InitPerSender does nothing in this module.
92+
func (s *Scanner) InitPerSender(senderID int) error {
93+
return nil
94+
}
95+
96+
// GetName returns the configured name for the Scanner.
97+
func (s *Scanner) GetName() string {
98+
return s.config.Name
99+
}
100+
101+
// GetTrigger returns the Trigger defined in the Flags.
102+
func (scanner *Scanner) GetTrigger() string {
103+
return scanner.config.Trigger
104+
}
105+
106+
// readResponse reads a response from the SOCKS5 server.
107+
func (conn *Connection) readResponse(expectedLength int) ([]byte, error) {
108+
resp := make([]byte, expectedLength)
109+
_, err := conn.conn.Read(resp)
110+
if err != nil {
111+
return nil, err
112+
}
113+
return resp, nil
114+
}
115+
116+
// sendCommand sends a command to the SOCKS5 server.
117+
func (conn *Connection) sendCommand(cmd []byte) error {
118+
_, err := conn.conn.Write(cmd)
119+
return err
120+
}
121+
122+
// explainResponse converts the raw response into a human-readable explanation.
123+
func explainResponse(resp []byte) map[string]string {
124+
if len(resp) < 10 {
125+
return map[string]string{"error": "response too short"}
126+
}
127+
128+
return map[string]string{
129+
"Version": fmt.Sprintf("0x%02x (SOCKS Version 5)", resp[0]),
130+
"Reply": fmt.Sprintf("0x%02x (%s)", resp[1], getReplyDescription(resp[1])),
131+
"Reserved": fmt.Sprintf("0x%02x", resp[2]),
132+
"Address Type": fmt.Sprintf("0x%02x (%s)", resp[3], getAddressTypeDescription(resp[3])),
133+
"Bound Address": fmt.Sprintf("%d.%d.%d.%d", resp[4], resp[5], resp[6], resp[7]),
134+
"Bound Port": fmt.Sprintf("%d", int(resp[8])<<8|int(resp[9])),
135+
}
136+
}
137+
138+
func getReplyDescription(code byte) string {
139+
switch code {
140+
case 0x00:
141+
return "succeeded"
142+
case 0x01:
143+
return "general SOCKS server failure"
144+
case 0x02:
145+
return "connection not allowed by ruleset"
146+
case 0x03:
147+
return "network unreachable"
148+
case 0x04:
149+
return "host unreachable"
150+
case 0x05:
151+
return "connection refused"
152+
case 0x06:
153+
return "TTL expired"
154+
case 0x07:
155+
return "command not supported"
156+
case 0x08:
157+
return "address type not supported"
158+
default:
159+
return "unassigned"
160+
}
161+
}
162+
163+
func getAddressTypeDescription(code byte) string {
164+
switch code {
165+
case 0x01:
166+
return "IPv4 address"
167+
case 0x03:
168+
return "Domain name"
169+
case 0x04:
170+
return "IPv6 address"
171+
default:
172+
return "unknown"
173+
}
174+
}
175+
176+
// PerformHandshake performs the SOCKS5 handshake.
177+
func (conn *Connection) PerformHandshake() (bool, error) {
178+
// Send version identifier/method selection message
179+
verMethodSel := []byte{0x05, 0x01, 0x00} // VER = 0x05, NMETHODS = 1, METHODS = 0x00 (NO AUTHENTICATION REQUIRED)
180+
err := conn.sendCommand(verMethodSel)
181+
if err != nil {
182+
return false, fmt.Errorf("error sending version identifier/method selection: %w", err)
183+
}
184+
conn.results.Version = "0x05"
185+
186+
// Read method selection response
187+
methodSelResp, err := conn.readResponse(2)
188+
if err != nil {
189+
return false, fmt.Errorf("error reading method selection response: %w", err)
190+
}
191+
conn.results.MethodSelection = fmt.Sprintf("%x", methodSelResp)
192+
193+
if methodSelResp[1] == 0xFF {
194+
return true, fmt.Errorf("no acceptable authentication methods")
195+
}
196+
197+
return false, nil
198+
}
199+
200+
// PerformConnectionRequest sends a connection request to the SOCKS5 server.
201+
func (conn *Connection) PerformConnectionRequest() error {
202+
// Send a connection request
203+
req := []byte{0x05, 0x01, 0x00, 0x01, 0xA6, 0x6F, 0x04, 0x64, 0x00, 0x50} // VER = 0x05, CMD = CONNECT, RSV = 0x00, ATYP = IPv4, DST.ADDR = 166.111.4.100, DST.PORT = 80
204+
err := conn.sendCommand(req)
205+
if err != nil {
206+
return fmt.Errorf("error sending connection request: %w", err)
207+
}
208+
209+
// Read connection response
210+
resp, err := conn.readResponse(10)
211+
if err != nil {
212+
return fmt.Errorf("error reading connection response: %w", err)
213+
}
214+
conn.results.ConnectionResponse = fmt.Sprintf("%x", resp)
215+
conn.results.ConnectionResponseExplanation = explainResponse(resp)
216+
217+
if resp[1] > 0x80 {
218+
return fmt.Errorf("connection request failed with response: %x", resp)
219+
}
220+
221+
return nil
222+
}
223+
224+
// Scan performs the configured scan on the SOCKS5 server.
225+
func (s *Scanner) Scan(t zgrab2.ScanTarget) (status zgrab2.ScanStatus, result interface{}, thrown error) {
226+
var err error
227+
var have_auth bool
228+
conn, err := t.Open(&s.config.BaseFlags)
229+
if err != nil {
230+
return zgrab2.TryGetScanStatus(err), nil, fmt.Errorf("error opening connection: %w", err)
231+
}
232+
cn := conn
233+
defer func() {
234+
cn.Close()
235+
}()
236+
237+
results := ScanResults{}
238+
socks5Conn := Connection{conn: cn, config: s.config, results: results}
239+
240+
have_auth, err = socks5Conn.PerformHandshake()
241+
if err != nil {
242+
if have_auth {
243+
return zgrab2.SCAN_SUCCESS, &socks5Conn.results, nil
244+
} else {
245+
return zgrab2.TryGetScanStatus(err), &socks5Conn.results, fmt.Errorf("error during handshake: %w", err)
246+
}
247+
}
248+
249+
err = socks5Conn.PerformConnectionRequest()
250+
if err != nil {
251+
return zgrab2.TryGetScanStatus(err), &socks5Conn.results, fmt.Errorf("error during connection request: %w", err)
252+
}
253+
254+
return zgrab2.SCAN_SUCCESS, &socks5Conn.results, nil
255+
}

zgrab2_schemas/zgrab2/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,6 @@
2222
from . import ipp
2323
from . import banner
2424
from . import amqp091
25+
from . import socks5
2526
from . import mqtt
2627
from . import pptp

zgrab2_schemas/zgrab2/socks5.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# zschema sub-schema for zgrab2's Socks5 module
2+
# Registers zgrab2-socks5 globally, and socks5 with the main zgrab2 schema.
3+
from zschema.leaves import *
4+
from zschema.compounds import *
5+
import zschema.registry
6+
7+
from . import zgrab2
8+
9+
# Schema for ScanResults struct
10+
socks5_response_explanation = SubRecord(
11+
{
12+
"Version": String(),
13+
"Reply": String(),
14+
"Reserved": String(),
15+
"Address Type": String(),
16+
"Bound Address": String(),
17+
"Bound Port": String(),
18+
}
19+
)
20+
21+
socks5_scan_response = SubRecord(
22+
{
23+
"version": String(),
24+
"method_selection": String(),
25+
"connection_response": String(),
26+
"connection_response_explanation": socks5_response_explanation,
27+
}
28+
)
29+
30+
socks5_scan = SubRecord(
31+
{
32+
"result": socks5_scan_response,
33+
},
34+
extends=zgrab2.base_scan_response,
35+
)
36+
37+
zschema.registry.register_schema("zgrab2-socks5", socks5_scan)
38+
zgrab2.register_scan_response_type("socks5", socks5_scan)

0 commit comments

Comments
 (0)