Skip to content

Commit

Permalink
add fingerprint validation (#50), and agent bind (#49)
Browse files Browse the repository at this point in the history
  • Loading branch information
NicoBBCha authored and Nicolas Chatelain committed Jun 22, 2024
1 parent 69cd37d commit bbb00ed
Show file tree
Hide file tree
Showing 8 changed files with 373 additions and 302 deletions.
53 changes: 50 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,11 @@ You use Ligolo-ng for your penetration tests? Did it help you pass a certificati
- [TLS Options](#tls-options)
- [Using Let's Encrypt Autocert](#using-lets-encrypt-autocert)
- [Using your own TLS certificates](#using-your-own-tls-certificates)
- [Automatic self-signed certificates (NOT RECOMMENDED)](#automatic-self-signed-certificates-not-recommended)
- [Automatic self-signed certificates](#automatic-self-signed-certificates)
- [Using Ligolo-ng](#using-ligolo-ng)
- [Agent Binding/Listening](#agent-bindinglistening)
- [Access to agent's local ports (127.0.0.1)](#access-to-agents-local-ports-127001)
- [Agent as server (Bind)](#agent-as-server-bind)
- [Demo](#demo)
- [Does it require Administrator/root access ?](#does-it-require-administratorroot-access-)
- [Supported protocols/packets](#supported-protocolspackets)
Expand All @@ -61,6 +62,7 @@ tunnels from a reverse TCP/TLS connection using a **tun interface** (without the
- Socket listening/binding on the *agent*
- Multiple platforms supported for the *agent*
- Can handle multiple tunnels
- Reverse/Bind Connection

## How is this different from Ligolo/Chisel/Meterpreter... ?

Expand Down Expand Up @@ -144,7 +146,27 @@ If you want to use your own certificates for the proxy server, you can use the `

The *proxy/relay* can automatically generate self-signed TLS certificates using the `-selfcert` option.

The `-ignore-cert` option needs to be used with the *agent*.
***Validating self-signed certificates fingerprints (recommended)***

When running selfcert, you can run the `certificate_fingerprint` command to print the currently used certificate fingerprint.

```
ligolo-ng » certificate_fingerprint
INFO[0203] TLS Certificate fingerprint for ligolo is: D005527D2683A8F2DB73022FBF23188E064493CFA17D6FCF257E14F4B692E0FC
```

On the agent, you can then connect using the fingerprint provided by the Ligolo-ng proxy.

```
ligolo-agent -connect 127.0.0.1:11601 -v -accept-fingerprint D005527D2683A8F2DB73022FBF23188E064493CFA17D6FCF257E14F4B692E0FC nchatelain@nworkstation
INFO[0000] Connection established addr="127.0.0.1:11601"
```

> By default, the "ligolo" domain name is used for TLS Certificate generation. You can change the domain by using the -selfcert-domain [domain] option at startup.
***Ignoring all certificate verification (for lab/debugging)***

To ignore all security mechanisms, the `-ignore-cert` option can be used with the *agent*.

> Beware of man-in-the-middle attacks! This option should only be used in a test environment or for debugging purposes.
### Using Ligolo-ng
Expand Down Expand Up @@ -195,7 +217,7 @@ Add a route on the *proxy/relay* server to the *192.168.0.0/24* *agent* network.
```shell
$ sudo ip route add 192.168.0.0/24 dev ligolo
```
**Or using the Ligolo-ng (>= 0.6) interface:**
**Or using the Ligolo-ng (>= 0.6) cli:**
```
ligolo-ng » interface_add_route --name evil-cha --route 192.168.2.0/24
INFO[3206] Route created.
Expand Down Expand Up @@ -298,6 +320,31 @@ Service detection performed. Please report any incorrect results at https://nmap
Nmap done: 1 IP address (1 host up) scanned in 7.16 seconds
```

### Agent as server (Bind)

The Ligolo-ng agent can operate using a bind connection (i.e. acting as a server).

Instead of using the `--connect [ip:port]` argument, you can use `--bind [ip:port]` so the agent start listening to connections.

After that, the proxy can connect to the agent using the `connect_agent` command.

In a terminal:
```
» ligolo-agent -bind 127.0.0.1:4444
WARN[0000] TLS Certificate fingerprint is: 05518ABE4F0D3B137A2365E0DE52A01FE052EE4C5A2FD12D8E2DD93AED1DD04B
INFO[0000] Listening on 127.0.0.1:4444...
INFO[0005] Got connection from: 127.0.0.1:53908
INFO[0005] Connection established addr="127.0.0.1:53908"
```

In ligolo-ng proxy:

```
ligolo-ng » connect_agent --ip 127.0.0.1:4444
? TLS Certificate Fingerprint is: 05518ABE4F0D3B137A2365E0DE52A01FE052EE4C5A2FD12D8E2DD93AED1DD04B, connect? Yes
INFO[0021] Agent connected. name=nchatelain@nworkstation remote="127.0.0.1:4444"
```

## Demo


Expand Down
65 changes: 58 additions & 7 deletions cmd/agent/main.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
package main

import (
"bytes"
"crypto/sha256"
"crypto/tls"
"crypto/x509"
"encoding/hex"
"flag"
"fmt"
"github.com/hashicorp/yamux"
"github.com/nicocha30/ligolo-ng/pkg/agent"
"github.com/nicocha30/ligolo-ng/pkg/utils/selfcert"
"github.com/sirupsen/logrus"
goproxy "golang.org/x/net/proxy"
"net"
Expand All @@ -22,12 +27,14 @@ var (
func main() {
var tlsConfig tls.Config
var ignoreCertificate = flag.Bool("ignore-cert", false, "ignore TLS certificate validation (dangerous), only for debug purposes")
var acceptFingerprint = flag.String("accept-fingerprint", "", "accept certificates matching the following SHA256 fingerprint (hex format)")
var verbose = flag.Bool("v", false, "enable verbose mode")
var retry = flag.Bool("retry", false, "auto-retry on error")
var socksProxy = flag.String("socks", "", "socks5 proxy address (ip:port)")
var socksUser = flag.String("socks-user", "", "socks5 username")
var socksPass = flag.String("socks-pass", "", "socks5 password")
var serverAddr = flag.String("connect", "", "the target (domain:port)")
var serverAddr = flag.String("connect", "", "connect to proxy (domain:port)")
var bindAddr = flag.String("bind", "", "bind to ip:port")

flag.Usage = func() {
fmt.Printf("Ligolo-ng %s / %s / %s\n", version, commit, date)
Expand All @@ -45,6 +52,36 @@ func main() {
logrus.SetLevel(logrus.DebugLevel)
}

if *bindAddr != "" {
selfcrt := selfcert.NewSelfCert(nil)
crt, err := selfcrt.GetCertificate(*bindAddr)
if err != nil {
logrus.Fatal(err)
}
logrus.Warnf("TLS Certificate fingerprint is: %X\n", sha256.Sum256(crt.Certificate[0]))
tlsConfig.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
return crt, nil
}
lis, err := net.Listen("tcp", *bindAddr)
if err != nil {
logrus.Fatal(err)
}
logrus.Infof("Listening on %s...", *bindAddr)
for {
conn, err := lis.Accept()
if err != nil {
logrus.Error(err)
continue
}
logrus.Infof("Got connection from: %s\n", conn.RemoteAddr())
tlsConn := tls.Server(conn, &tlsConfig)

if err := connect(tlsConn); err != nil {
logrus.Error(err)
}
}
}

if *serverAddr == "" {
logrus.Fatal("please, specify the target host user -connect host:port")
}
Expand All @@ -71,7 +108,23 @@ func main() {
conn, err = net.Dial("tcp", *serverAddr)
}
if err == nil {
err = connect(conn, &tlsConfig)
if *acceptFingerprint != "" {
tlsConfig.InsecureSkipVerify = true
tlsConfig.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
crtFingerprint := sha256.Sum256(rawCerts[0])
crtMatch, err := hex.DecodeString(*acceptFingerprint)
if err != nil {
return fmt.Errorf("invalid cert fingerprint: %v\n", err)
}
if bytes.Compare(crtMatch, crtFingerprint[:]) != 0 {
return fmt.Errorf("certificate does not match fingerprint: %X != %X", crtFingerprint, crtMatch)
}
return nil
}
}
tlsConn := tls.Client(conn, &tlsConfig)

err = connect(tlsConn)
}
logrus.Errorf("Connection error: %v", err)
if *retry {
Expand All @@ -94,15 +147,13 @@ func sockDial(serverAddr string, socksProxy string, socksUser string, socksPass
return proxyDialer.Dial("tcp", serverAddr)
}

func connect(conn net.Conn, config *tls.Config) error {
tlsConn := tls.Client(conn, config)

yamuxConn, err := yamux.Server(tlsConn, yamux.DefaultConfig())
func connect(conn net.Conn) error {
yamuxConn, err := yamux.Server(conn, yamux.DefaultConfig())
if err != nil {
return err
}

logrus.WithFields(logrus.Fields{"addr": tlsConn.RemoteAddr()}).Info("Connection established")
logrus.WithFields(logrus.Fields{"addr": conn.RemoteAddr()}).Info("Connection established")

for {
conn, err := yamuxConn.Accept()
Expand Down
77 changes: 77 additions & 0 deletions cmd/proxy/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ package app

import (
"context"
"crypto/sha256"
"crypto/tls"
"errors"
"fmt"
"github.com/AlecAivazis/survey/v2"
"github.com/desertbit/grumble"
"github.com/hashicorp/yamux"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/nicocha30/ligolo-ng/pkg/controller"
"github.com/nicocha30/ligolo-ng/pkg/proxy"
Expand All @@ -22,6 +25,7 @@ var AgentList map[int]*controller.LigoloAgent
var AgentListMutex sync.Mutex
var ListenerList map[int]controller.Listener
var ListenerListMutex sync.Mutex
var ProxyController *controller.Controller

var (
ErrInvalidAgent = errors.New("please, select an agent using the session command")
Expand All @@ -36,6 +40,13 @@ func RegisterAgent(agent *controller.LigoloAgent) error {
return nil
}

func UnregisterAgent(agent *controller.LigoloAgent) error {
AgentListMutex.Lock()
delete(AgentList, agent.Id)
AgentListMutex.Unlock()
return nil
}

func Run() {
// CurrentAgent points to the selected agent in the UI (when running session)
var CurrentAgentID int
Expand Down Expand Up @@ -86,6 +97,72 @@ func Run() {
},
})

App.AddCommand(&grumble.Command{
Name: "certificate_fingerprint",
Help: "Show the current selfcert fingerprint",
Usage: "certificate_fingerprint",

Run: func(c *grumble.Context) error {
selfcrt := ProxyController.SelfCert
if selfcrt == nil {
return errors.New("certificate is nil")
}
logrus.Printf("TLS Certificate fingerprint for %s is: %X\n", ProxyController.SelfcertDomain, sha256.Sum256(selfcrt.Certificate[0]))

return nil
},
})

App.AddCommand(&grumble.Command{
Name: "connect_agent",
Help: "Attempt to connect to a bind agent",
Usage: "connect_agent --ip [agentip]",
Flags: func(f *grumble.Flags) {
f.StringL("ip", "", "The agent ip:port")
f.BoolL("ignore-cert", false, "Ignore TLS certificate verification")
},
Run: func(c *grumble.Context) error {
tlsConfig := &tls.Config{}
tlsConfig.InsecureSkipVerify = true

remoteConn, err := tls.Dial("tcp", c.Flags.String("ip"), tlsConfig)
if err != nil {
return err
}
if !c.Flags.Bool("ignore-cert") {
cert := remoteConn.ConnectionState().PeerCertificates[0].Raw
shaSum := sha256.Sum256(cert)
confirmTLS := false
prompt := &survey.Confirm{
Message: fmt.Sprintf("TLS Certificate Fingerprint is: %X, connect?", shaSum),
}
survey.AskOne(prompt, &confirmTLS)
if !confirmTLS {
remoteConn.Close()
return errors.New("connection aborted (user did not validate TLS cert)")
}
}

yamuxConn, err := yamux.Client(remoteConn, nil)
if err != nil {
return err
}

agent, err := controller.NewAgent(yamuxConn)
if err != nil {
logrus.Errorf("could not register agent, error: %v", err)
return err
}

logrus.WithFields(logrus.Fields{"remote": remoteConn.RemoteAddr(), "name": agent.Name}).Info("Agent connected.")

if err := RegisterAgent(agent); err != nil {
logrus.Errorf("could not register agent: %s", err.Error())
}
return nil
},
})

App.AddCommand(&grumble.Command{
Name: "tunnel_start",
Help: "Start relaying connection to the current agent",
Expand Down
25 changes: 24 additions & 1 deletion cmd/proxy/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ func main() {
var certFile = flag.String("certfile", "certs/cert.pem", "TLS server certificate")
var keyFile = flag.String("keyfile", "certs/key.pem", "TLS server key")
var domainWhitelist = flag.String("allow-domains", "", "autocert authorised domains, if empty, allow all domains, multiple domains should be comma-separated.")
var selfcertDomain = flag.String("selfcert-domain", "ligolo", "The selfcert TLS domain to use")
var versionFlag = flag.Bool("version", false, "show the current version")

flag.Usage = func() {
Expand Down Expand Up @@ -66,6 +67,10 @@ func main() {
a.Printf(" Version: %s\n\n", version)
})

if *enableSelfcert && *selfcertDomain == "ligolo" {
logrus.Warning("Using default selfcert domain 'ligolo', beware of CTI, SOC and IoC!")
}

app.Run()

proxyController := controller.New(controller.ControllerConfig{
Expand All @@ -75,7 +80,9 @@ func main() {
Certfile: *certFile,
Keyfile: *keyFile,
DomainWhitelist: allowDomains,
SelfcertDomain: *selfcertDomain,
})
app.ProxyController = &proxyController

go proxyController.ListenAndServe()

Expand All @@ -91,7 +98,8 @@ func main() {

yamuxConn, err := yamux.Client(remoteConn, nil)
if err != nil {
panic(err)
logrus.Errorf("could not create yamux client, error: %v", err)
continue
}

agent, err := controller.NewAgent(yamuxConn)
Expand All @@ -105,6 +113,21 @@ func main() {
if err := app.RegisterAgent(agent); err != nil {
logrus.Errorf("could not register agent: %s", err.Error())
}

go func() {
// Check agent status
for {
select {
case <-agent.Session.CloseChan(): // Agent closed
logrus.Warnf("Lost ligolo-ng connection with agent %s!", agent.Name)
if err := app.UnregisterAgent(agent); err != nil {
logrus.Errorf("could not unregister agent: %s", err.Error())
}
return
}
}
}()

}
}()

Expand Down
Loading

0 comments on commit bbb00ed

Please sign in to comment.