Local file inclusion - Go

Local file inclusion - Go

Need

Prevention of unauthorized access to sensitive files on the server

Context

  • Usage of Go 1.16 for building efficient and scalable applications
  • Usage of gin-gonic/gin for building web applications in Go
  • Usage of io/ioutil for reading and writing files in Node.js
  • Usage of net/http for building HTTP servers in a Node.js application

Description

Non compliant code

        package main

import (
	"github.com/gin-gonic/gin"
	"io/ioutil"
	"net/http"
)

func main() {
	r := gin.Default()

	r.GET("/readfile", func(c *gin.Context) {
		filepath := c.Query("filepath")
		data, err := ioutil.ReadFile(filepath)
		if err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
			return
		}
		c.String(http.StatusOK, string(data))
	})

	r.Run()
}
        
        

The above Go code uses the Gin web framework to create a simple web server. The server has a single endpoint, /readfile, which reads a file from the server's filesystem and returns its content.

The file path is taken directly from the filepath query parameter in the GET request. This is done using the c.Query("filepath") function call. The file at that path is then read using ioutil.ReadFile(filepath), and its content is returned as a response.

The major vulnerability in this code is Local File Inclusion (LFI). This vulnerability occurs because the application uses input from the user to construct a file path to read, without properly validating or sanitizing the input. This can allow an attacker to read any file on the system that the web server has access to, simply by providing a path to that file. For example, an attacker could use this vulnerability to read sensitive files like /etc/passwd.

To exploit this vulnerability, an attacker could send a GET request to the /readfile endpoint with a filepath query parameter that includes a relative or absolute path to a file they want to read. For example, http://localhost:8080/readfile?filepath=/etc/passwd.

This vulnerability can lead to serious security issues, like information disclosure, and can potentially be used as a stepping stone for further attacks, like Remote Code Execution (RCE), if the server also interprets the file content.

Steps

  • Validate user input: Implement input validation to ensure that the filepath provided by the user is a valid and safe path.
  • Restrict access to specific directories: Limit the user's ability to access files outside of a specific directory by enforcing a whitelist of allowed directories.
  • Sanitize user input: Use a sanitization mechanism to remove any potentially harmful characters or sequences from the filepath input.
  • Use absolute file paths: Instead of relying on relative file paths, use absolute file paths to ensure that the application only accesses the intended files.
  • Implement access controls: Implement access controls to restrict the user's ability to read or execute certain files based on their role or permissions.
  • Implement file permissions: Set appropriate file permissions on the server to restrict access to sensitive files.
  • Use a secure file handling library: Utilize a secure file handling library that provides built-in protection against file inclusion vulnerabilities.

Compliant code

        package main

import (
	"github.com/gin-gonic/gin"
	"io/ioutil"
	"net/http"
	"path/filepath"
	"strings"
)

func main() {
	r := gin.Default()

	r.GET("/readfile", func(c *gin.Context) {
		inputPath := c.Query("filepath")

		// 1. Validate user input
		if inputPath == "" {
			c.JSON(http.StatusBadRequest, gin.H{"error": "File path is required"})
			return
		}

		// 2. Restrict access to specific directories
		// 4. Use absolute file paths
		basePath := "/path/to/allowed/directory"
		absPath := filepath.Join(basePath, filepath.Clean("/"+inputPath))

		// 3. Sanitize user input
		if !strings.HasPrefix(absPath, basePath) {
			c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid file path"})
			return
		}

		data, err := ioutil.ReadFile(absPath)
		if err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
			return
		}
		c.String(http.StatusOK, string(data))
	})

	r.Run()
}
        
        

The updated code includes several measures to prevent local file inclusion vulnerabilities:

1. Validate user input: The code checks if the filepath query parameter is provided. If not, it returns a 400 Bad Request error.

2. Restrict access to specific directories: The code defines a basePath which is the only directory that the user can access. This is a form of whitelisting, which is more secure than blacklisting (trying to block access to specific directories).

3. Sanitize user input: The filepath.Clean function is used to remove any .. or similar sequences from the input path. Then, the code checks if the resulting absolute path still starts with the basePath. If not, it returns a 400 Bad Request error.

4. Use absolute file paths: The filepath.Join function is used to construct an absolute path from the basePath and the sanitized input path. This ensures that the application only accesses files within the basePath directory.

Please note that this code does not implement the following measures:

- Implement access controls: Depending on your application, you might want to add additional checks to ensure that the user is authorized to access the requested file.

- Implement file permissions: You should ensure that the file permissions on your server are set up correctly to prevent unauthorized access.

- Use a secure file handling library: This code uses the standard ioutil.ReadFile function to read the file. If you are dealing with more complex file operations, you might want to consider using a library that provides additional security features.

References