Skip to content

Instantly share code, notes, and snippets.

@lvidarte
Created October 30, 2017 10:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lvidarte/a468ce6db648c0580faadb201d107c0f to your computer and use it in GitHub Desktop.
Save lvidarte/a468ce6db648c0580faadb201d107c0f to your computer and use it in GitHub Desktop.
package main
import (
"bufio"
"bytes"
"crypto/tls"
"encoding/hex"
"encoding/json"
"errors"
"io"
"log"
"net"
"net/http"
"net/http/httputil"
"net/url"
"strings"
"time"
)
var (
shellinaboxAddress string = "shellinabox:4200"
proxyPort string = "443"
slackURL string = "https://hooks.slack.com/services/<USER>/<CHANNEL>"
sessions = make(Sessions)
sessionTTL int = 60 // 1 minute
sessionMonitorInterval int = 60 // 1 minute
)
/** Slack Message **/
type Message struct {
Channel string `json:"channel"`
Username string `json:"username"`
IconEmoji string `json:"icon_emoji"`
Text string `json:"text"`
}
/** ShellInABox Session **/
type Session struct {
IP string
History []string
HIndex int
HLineIndex int
StartAt time.Time
LastActivity time.Time
}
func NewSession(ip string) *Session {
return &Session{
IP: ip,
History: append(make([]string, 0), ""),
HIndex: 0,
HLineIndex: 0,
StartAt: time.Now(),
LastActivity: time.Now(),
}
}
func (s *Session) Add(keys string) {
switch keys {
case "0D": // enter
if s.History[s.HIndex] != "" {
s.HIndex++
s.HLineIndex = 0
s.History = append(s.History, "")
}
case "7F": // del
if s.HLineIndex > 0 {
s.History[s.HIndex] = s.History[s.HIndex][:s.HLineIndex-1] +
s.History[s.HIndex][s.HLineIndex:]
s.HLineIndex--
}
case "03": // C-c
s.History[s.HIndex] = ""
s.HLineIndex = 0
case "04": // C-d
s.History[s.HIndex] += "<C-d>"
s.HIndex++
s.HLineIndex = 0
s.History = append(s.History, "")
case "1A": // C-z
case "09": // Tab
case "1B5B41": // up arrow key
case "1B5B42": // down arrow key
case "1B5B43": // right arrow key
if s.HLineIndex < len(s.History[s.HIndex]) {
s.HLineIndex++
}
case "1B5B44": // left arrow key
if s.HLineIndex > 0 {
s.HLineIndex--
}
default:
if len(keys) == 2 {
char, _ := hex.DecodeString(keys)
if s.HLineIndex > 0 {
s.History[s.HIndex] = s.History[s.HIndex][:s.HLineIndex] +
string(char) +
s.History[s.HIndex][s.HLineIndex:]
} else {
s.History[s.HIndex] = string(char)
}
s.HLineIndex++
}
}
}
/** Sessions **/
type Sessions map[string]*Session
/** Functions **/
func main() {
/** Load ssl certificates **/
cer, err := tls.LoadX509KeyPair(
"/etc/shellinabox-proxy/ssl/server.crt",
"/etc/shellinabox-proxy/ssl/server.key",
)
if err != nil {
log.Fatal(err)
return
}
config := &tls.Config{Certificates: []tls.Certificate{cer}}
ln, err := tls.Listen("tcp", ":"+proxyPort, config)
if err != nil {
log.Fatal(err)
return
}
log.Printf("ShellInABox proxy started at port %s", proxyPort)
/** Monitor sessions timeout **/
go sessionsMonitor()
for {
if conn, err := ln.Accept(); err == nil {
go handleConnection(conn)
}
}
}
func sessionsMonitor() {
for range time.Tick(time.Second * time.Duration(sessionMonitorInterval)) {
log.Printf("Sessions monitor - Active sessions: %d", len(sessions))
for id, session := range sessions {
if time.Since(session.LastActivity).Seconds() > float64(sessionTTL) {
log.Printf("Sessions monitor - Session %s timeout - IP %s - StartAt %s - LastActivity %s - History:\n%s",
id,
session.IP,
session.StartAt.Format(time.RFC3339),
session.LastActivity.Format(time.RFC3339),
strings.Join(session.History[:], "\n"),
)
delete(sessions, id)
}
}
}
}
func handleConnection(conn net.Conn) {
defer conn.Close()
reader := bufio.NewReader(conn)
for {
req, err := http.ReadRequest(reader)
if err != nil {
if err != io.EOF {
log.Fatalf("Failed to read request: %s", err)
}
return
}
// Store history for session.
if req.Method == "POST" {
sessionStore(conn, req)
}
// Connect to a backend and send the request along.
if be, err := net.Dial("tcp", shellinaboxAddress); err == nil {
be_reader := bufio.NewReader(be)
if err := req.Write(be); err == nil {
if resp, err := http.ReadResponse(be_reader, req); err == nil {
FixHttp10Response(resp, req)
if err := resp.Write(conn); err == nil {
//log.Printf("proxied %s: got %d", req.URL.Path, resp.StatusCode)
}
if resp.Close {
return
}
}
}
}
}
}
func sessionStore(conn net.Conn, req *http.Request) {
session, keys := parseRequest(req)
if session == "" {
return
}
if _, ok := sessions[session]; !ok {
log.Printf("New session %s", session)
ip := strings.Split(conn.RemoteAddr().String(), ":")
sessions[session] = NewSession(ip[0])
err := slackNotify(sessions[session])
if err != nil {
log.Fatal(err)
}
}
sessions[session].LastActivity = time.Now()
if keys != "" {
sessions[session].Add(keys)
}
}
func parseRequest(req *http.Request) (string, string) {
dump, _ := httputil.DumpRequest(req, true)
rawRequest := string(dump)
query := rawRequest[strings.Index(rawRequest, "\r\n\r\n")+4:]
path, _ := url.Parse("/?" + query)
args := path.Query()
return args.Get("session"), args.Get("keys")
}
func FixHttp10Response(resp *http.Response, req *http.Request) {
if req.ProtoMajor == 1 && req.ProtoMinor == 1 {
return
}
resp.Proto = "HTTP/1.0"
resp.ProtoMajor = 1
resp.ProtoMinor = 0
if strings.Contains(strings.ToLower(req.Header.Get("Connection")), "keep-alive") {
resp.Header.Set("Connection", "keep-alive")
resp.Close = false
} else {
resp.Close = true
}
}
func slackNotify(session *Session) error {
return nil
message := &Message{
Channel: "#channel",
Username: "webhookbot",
IconEmoji: ":ghost:",
Text: "Attemp to tunnel from remote ip " + session.IP,
}
message_json, err := json.Marshal(message)
if err != nil {
return err
}
req, err := http.NewRequest("POST",
slackURL, bytes.NewBuffer(message_json),
)
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
res, err := client.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
if res.StatusCode != 200 {
return errors.New(res.Status)
}
return nil
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment