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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user