Init
This commit is contained in:
commit
deb85fe10d
29
README.md
Normal file
29
README.md
Normal file
@ -0,0 +1,29 @@
|
||||
# Ghost
|
||||
|
||||
*Your friendly helper with /etc/hosts, written in Go!*
|
||||
|
||||
## Features
|
||||
|
||||
- Extending existing lines
|
||||
- Deduplication
|
||||
- Format check
|
||||
- `SUID` safe!
|
||||
|
||||
## Usage
|
||||
|
||||
To add two domain names, `domain1.foo` and `domain2.bar`, for the IP addres `10.0.0.1`:
|
||||
|
||||
`./ghost 10.0.0.1 domain1.foo domain2.bar`
|
||||
|
||||
The added line will look like this:
|
||||
|
||||
```
|
||||
10.0.0.1 domain1.foo domain2.bar # Added by ghost on 26-10-2023
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
The tool is safe to use with the [SUID bit](https://www.redhat.com/sysadmin/suid-sgid-sticky-bit), which is very handy for not entering your password when adding hostnames to your `/etc/hosts` file every time.
|
||||
|
||||
While this is very handy for CTFs or platforms like Hack The Box, this is also a big security issue, e.g. if you are on a multi-user machine. **Use with caution!**
|
||||
|
130
main.go
Normal file
130
main.go
Normal file
@ -0,0 +1,130 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
targetIP, domains := parseArgs()
|
||||
motd()
|
||||
process(targetIP, domains)
|
||||
}
|
||||
|
||||
func parseArgs() (net.IP, []string) {
|
||||
// Check for arguments
|
||||
if len(os.Args) < 3 {
|
||||
usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Parse IP
|
||||
rawIP := os.Args[1]
|
||||
ip := net.ParseIP(rawIP)
|
||||
if ip == nil {
|
||||
fmt.Fprintf(os.Stderr, "Not an IP address: %s\n", rawIP)
|
||||
usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Check for formatting of domain names
|
||||
domainExpr, _ := regexp.Compile("^([a-zA-Z0-9]+[a-zA-Z0-9\\-]*[a-zA-Z0-9]+.{0,1})+$")
|
||||
for _, a := range os.Args[2:] {
|
||||
if !domainExpr.MatchString(a) {
|
||||
fmt.Fprintf(os.Stderr, "Doesn't look like a valid domain: %s\n", a)
|
||||
usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// All parsed and correct
|
||||
return ip, os.Args[2:]
|
||||
}
|
||||
|
||||
// process goes over the /etc/hosts and tries to best-fit the domain names for the IP
|
||||
func process(ip net.IP, domains []string) {
|
||||
// Read file
|
||||
bytesEtcHosts, readErr := os.ReadFile("/etc/hosts")
|
||||
if readErr != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to read /etc/hosts: %s\n", readErr.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
etcHosts := string(bytesEtcHosts)
|
||||
|
||||
// Iterate over lines, find the first match for the IP
|
||||
lines := strings.Split(etcHosts, "\n")
|
||||
ipString := ip.String()
|
||||
found := false
|
||||
for lPos, l := range lines {
|
||||
if strings.HasPrefix(l, ipString) {
|
||||
// Matching line, append our domains.
|
||||
|
||||
// Avoid duplicates
|
||||
fields := strings.Fields(l)
|
||||
newDomainsArray := []string{}
|
||||
ignore := false
|
||||
for _, d := range domains {
|
||||
for _, f := range fields {
|
||||
if d == f {
|
||||
// this domain name is already in the hosts file, skip
|
||||
ignore = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !ignore {
|
||||
newDomainsArray = append(newDomainsArray, d)
|
||||
}
|
||||
}
|
||||
newDomains := strings.Join(newDomainsArray, " ")
|
||||
|
||||
// Preserve comments and construct the line again
|
||||
hostLine, comment, hasComment := strings.Cut(l, "#")
|
||||
if hasComment {
|
||||
lines[lPos] = fmt.Sprintf("%s %s # %s", hostLine, newDomains, comment)
|
||||
} else {
|
||||
lines[lPos] = fmt.Sprintf("%s %s", hostLine, newDomains)
|
||||
}
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
// If a fitting line was not found in the previous for loop, append a new line
|
||||
if !found {
|
||||
newDomains := strings.Join(domains, " ")
|
||||
date := time.Now().Format("02-01-2006")
|
||||
newLine := fmt.Sprintf("%s\t%s # Added by ghost on %s", ipString, newDomains, date)
|
||||
lines = append(lines, newLine)
|
||||
}
|
||||
|
||||
// Write out again
|
||||
newHosts := strings.Join(lines, "\n")
|
||||
writeErr := os.WriteFile("/etc/hosts", []byte(newHosts), 0o644)
|
||||
if writeErr != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to write /etc/hosts: %s\n", writeErr.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// usage prints the usage
|
||||
func usage() {
|
||||
fmt.Fprintf(os.Stderr, "Usage: ghost 10.0.1.1 server1.gh <foo.server2.gh> <www.server2.gh> ...\n")
|
||||
}
|
||||
|
||||
// motd prints the banner at the start, fully easter-egg free
|
||||
func motd() {
|
||||
fmt.Println("+-------------------------+")
|
||||
if(time.Now().Month() == time.October) {
|
||||
// I mean, it's called 'ghost' after all...
|
||||
fmt.Println("👻 🕷️ 🎃 G H O S T 🎃 🕷️ 👻")
|
||||
fmt.Println(" your spooooky helper with ")
|
||||
} else {
|
||||
fmt.Println(" G H O S T ")
|
||||
fmt.Println(" your friendly helper with ")
|
||||
}
|
||||
fmt.Println(" /etc/hosts, written in Go!")
|
||||
fmt.Println("+-------------------------+")
|
||||
}
|
Loading…
Reference in New Issue
Block a user