This article aims to provide tips for writing secure code in Golang. However, these tips are applicable to other programming languages as well.
I Don’t Care if My Code is Secure or Not, It Works!
Don’t think like this. It’s important to protect your code from attackers. Sometimes writing secure code can be overwhelming but it worth it.
Vulnerabilities can pose serious problems. If you are working on a personal project, it might not be a big issue, but if you are developing something for public use, you should be concerned about users’ privacy and security.
This article is designed to help you delve into secure coding principles and understand the concepts of secure coding without consuming too much energy.
Avoid Giving Users Unnecessary Privileges
I want to start a program that users request, and I’ll just use exec
to make it happen! It’s simple and effective. No It’s not a good usage.
Using exec might seem like a shortcut to supercharge your code, but it also opens the door to serious security flaws such as:
- Arbitrary Code Execution: Allows running any code.
- Injection Attacks: Opens up to code injection.
- Unintended Side Effects: Can cause crashes or data corruption.
- Difficulty in Debugging: Makes code harder to maintain and debug.
Indeed, ‘exec’ is merely one example; another common instance involves granting users SQL access through SQL injection vulnerabilities.
import (
"database/sql"
"net/http"
)
func search_products(w http.ResponseWriter, r *http.Request) {
keyword := r.URL.Query().Get("keyword")
query := "SELECT * FROM products WHERE name LIKE '%' || $1 || '%'"
rows, err := db.Query(query, keyword)
if err != nil {
http.Error(w, "Error executing query", http.StatusInternalServerError)
return
}
defer rows.Close()
}
You didn’t check the user’s keyword. This oversight could lead to potential SQL injection vulnerabilities. Let’s see the case.
SELECT * FROM products WHERE name LIKE '%' OR 1=1; -- '%'
The oversight of not validating the user’s keyword allows attackers to inject a statement that returns true, resulting in the system listing all products from the database.
This vulnerability can also enable attackers to perform actions like removing products from the database.
Stop Use Unencrypted Protocols
Don’t use HTTP; use HTTPS! Yes, you’ve probably heard this advice a thousand times, but it’s worth emphasizing once more. It’s crucial to use encrypted protocols for communication, which is a fundamental aspect of secure code development.
After all, nobody wants their conversations to be eavesdropped on, right? Using HTTPS ensures the confidentiality and integrity of the data exchanged between your application and the API server.
package main
import (
"fmt"
"net/http"
"strings"
)
func main() {
var city string
fmt.Print("City: ")
fmt.Scan(&city)
city = strings.Title(strings.ToLower(city))
// API connection
api_key := "useyourapikey"
url := fmt.Sprintf("https://api.openweathermap.org/data/2.5/weather?q=%s&appid=%s", city, api_key)
resp, err := http.Get(url)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
fmt.Printf("Error: %s\n", resp.Status)
return
}
fmt.Println("API call successful.")
}
Ensure Your Libraries Are Updated and Maintained
Keeping your libraries updated and maintained is essential. It ensures your application stays secure, stable, and compatible.
Updates often include crucial security patches, new features, and performance improvements. By staying current, you reduce the risk of vulnerabilities and compatibility issues.
Regular updates also demonstrate your commitment to security and reliability, fostering trust among users and stakeholders. Thus, prioritizing library updates is crucial for safeguarding your application and ensuring its smooth operation.
Handle Errors
Giving detail is important users should know what’s going on also you should understand the problem that caused by program.
However, it’s important to avoid providing unnecessary knowledge and system information to attackers. Don’t expose system details!
package main
import (
"fmt"
"net/http"
)
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Error: Page not found\n")
}
func main() {
http.HandleFunc("/", handler)
http.ListenAndServe(":8080", nil)
}
In this message, we simply inform the user about a ‘Page Not Found’ error. We don’t provide any additional information besides describing the problem.
package main
import (
"fmt"
"net/http"
)
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Error: Page not found\n")
fmt.Fprintf(w, "User Agent: %s\n", r.UserAgent())
}
func main() {
http.HandleFunc("/", handler)
http.ListenAndServeTLS(":8080", nil)
}
In this sample, we exposed the user agent unnecessarily. Revealing the user agent to the user not only makes the message harder to understand but also introduces potential security risks.
Logging and No-Logging
Actually, logging certain events and sending them to developers can help them understand issues and facilitate problem-solving.
However, it’s important to acknowledge that logging can also introduce security risks. Therefore, it’s up to you to decide whether to implement logging or adopt a no-log policy.
Regardless, it’s crucial to avoid logging personal information. Personally, I prefer not to log anything at all.
package main
import (
"log"
)
func main() {
logger := log.New(log.Writer(), "info: ", log.LstdFlags)
logger.Println("Application started successfully")
err := something()
if err != nil {
logger.Printf("Error occurred: %v", err)
}
}
func something() error {
// Simulate an error
return nil
}
In this example, we don’t store any personal information about the system or user; we simply log errors. Of course, you can add more details to this log, such as allowing users to describe what happened when the error occurred, and save this information to make debugging process easy.
package main
import (
"log"
)
func main() {
// Using the default logger without specifying settings
log.Println("Application Started Successfully")
// Logging errors without context
err := something()
if err != nil {
log.Println(err)
}
}
func something() error {
// Simulate an error
return nil
}
It’s important to provide details about errors in logs to facilitate debugging. Additionally, including the date and time in logs helps to track when errors occurred. In this code snippet, we’re logging errors without any accompanying information, making it challenging to debug effectively.
Don’t Store Unnecessary Data
Storing unnecessary data can lead to various issues, including increased storage costs, slower performance, and potential security risks.
It’s essential to evaluate what data needs to be stored based on business requirements and regulatory compliance. Storing only essential data minimizes the risk of data breaches and ensures compliance with privacy regulations.
Additionally, reducing data storage can improve system performance and simplify data management processes. By adopting a policy of storing only necessary data, organizations can optimize resource utilization, enhance data security, and mitigate potential risks associated with storing excessive or redundant data.
if you work at Google, you can forget this part.
Conclusion
In conclusion, prioritizing security in your code is vital. By following secure coding practices like minimizing unnecessary data storage and implementing HTTPS, you can enhance your application’s defenses.
Remember, security is an ongoing process, and while these tips provide a solid foundation, there may be additional considerations for your project.
Explore more cybersecurity articles