Güvenli kodlama: Yol geçişine karşı Java NIO'yu kullanmaya yönelik en iyi uygulamalar

Adanali

Member
Güvenli Kodlama: Yol Geçişi (CWE-22) aracılığıyla yetkisiz erişimin engellenmesi yazısında, CWE-22 (Path Traversal) kritik güvenlik açığı nedeniyle yazılım geliştirmede ortaya çıkabilecek tehlikeler ve geliştiricilerin bunlara karşı önlem alabileceği önlemler anlatılıyor. BT. Aşağıda özellikle Java'nın Yeni G/Ç (NIO) paket araçlarının CWE-22 ile ilişkili risklerle mücadele etmek için nasıl kullanılabileceği ele alınmaktadır. En iyi uygulamalar, NIO'nun güçlü dosya ve yol yönetimi araçlarının özellikle yol geçişindeki güvenlik açıklarını önlemek için nasıl kullanılabileceğini adım adım göstermek için kullanılır.


Duyuru








Sven, 1996'dan bu yana ve 15 yılı aşkın bir süredir dünya çapında otomotiv, havacılık, sigorta, bankacılık, Birleşmiş Milletler ve Dünya Bankası gibi endüstrilerde endüstriyel projelerde Java programlıyor. 10 yıldan fazla bir süredir Amerika'dan Yeni Zelanda'ya kadar konferanslarda ve topluluk etkinliklerinde konuşmacı olarak yer alıyor, JFrog ve Vaadin için geliştirici savunucusu olarak çalışıyor ve düzenli olarak BT dergileri ve teknoloji portalları için makaleler yazıyor. Ana konusu olan Core Java'nın yanı sıra TDD ve güvenli kodlama uygulamalarını da kapsar.







Yolları normalleştirin


Normalleştirme, “.” gibi bir yoldaki tüm gereksiz öğeleri kaldırır. (geçerli dizin) ve “..” (kök dizin). Bu süreç, yolların uygun şekilde yapılandırılmasını sağlamaya yardımcı olur ve yol geçiş saldırılarını önler.

Temel yol oluştur: Kullanıcı girişinin çözümleneceği temel dizini tanımlar. Erişimi kısıtlamak istediğiniz dizin bu olmalıdır.


Path basePath = Paths.get("/var/www/uploads").normalize();


Kullanıcı girişini çözümleyin: Tam bir yol oluşturmak için temel yolu kullanıcı tarafından sağlanan girişle birleştirin. Bu adım, kullanıcı tarafından belirlenen ilgili yolların temel yol bağlamında yorumlanmasını sağlamak açısından kritik öneme sahiptir.


String userInput = request.getParameter("file");
Path resolvedPath = basePath.resolve(userInput);


Tüm gereksiz öğeleri kaldırmak için çözümlenen yolu normalleştirin. Bu adım tüm “.” veya yoldaki “..” doğru şekilde çözülebilir.


Path normalizedPath = resolvedPath.normalize();


Normalleştirilmiş yolu doğrulayın: Normalleştirilmiş yolun temel yolla başladığından emin olun. Bu kontrol, yolun beklenen dizinin dışına çıkmamasını sağlar.


if (!normalizedPath.startsWith(basePath)) {
throw new SecurityException("Invalid file path: path traversal attempt detected.");
}


Temel dizinde kaldığından emin olmak için yolu kontrol edin: Temel dizinin en basit biçimine göre normalleştirildiğinden emin olun. Temel dizini kullanıcı tarafından sağlanan yolla birleştirin, ardından ortaya çıkan yolu normalleştirin. Hedeflenen dizinin dışına çıkmamasını sağlamak için, son normalleştirilmiş yolu temel dizinle başlayacak şekilde ayarlayın.

Güvenli dizinleri ve dosya izinlerini kullanın

Güçlü dizin ve dosya izinlerinin kullanılması, özellikle Java uygulamalarında kullanıcı girişi yapılırken, dosya ve dizinlerin güvenliğini ve bütünlüğünü sağlamak açısından önemlidir.

Java NIO, dosya izinlerini ayarlamak ve kontrol etmek için API'ler sağlar. Sınıflar PosixFilePermissions VE PosixFileAttributeView bunu yapabilir.

Bir dizindeki izinleri ayarlama

Bir dizindeki izinleri ayarlamak için şunları yapabilirsiniz: Files.createDirectories VE PosixFilePermissions kullanım:


import java.nio.file.*;
import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.PosixFilePermissions;
import java.util.Set;
import java.io.IOException;

public class SecureFileHandler {

/**
* Creates a directory with secure permissions.
*
* @param dirPath The path of the directory to create.
* @throws IOException if an I/O error occurs.
*/
public static void createSecureDirectory(Path dirPath) throws IOException {
Set<PosixFilePermission> perms = PosixFilePermissions.fromString("rwxr-x---");
FileAttribute<Set<PosixFilePermission>> attr
= PosixFilePermissions.asFileAttribute(perms);
Files.createDirectories(dirPath, attr);
}

/**
* Example usage of creating a secure directory.
*
* @param args Command line arguments.
*/
public static void main(String[] args) {
Path dirPath = Paths.get("/var/www/uploads");
try {
createSecureDirectory(dirPath);
} catch (IOException e) {
e.printStackTrace();
}
}
}


Dosya izinlerini ayarlama

Benzer şekilde, yöntemi kullanarak dosya izinlerini ayarlayabilirsiniz. Files.setPosixFilePermissions kurmak:


import java.nio.file.*;
import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.PosixFilePermissions;
import java.io.IOException;
import java.util.Set;

public class SecureFileHandler {

/**
* Sets secure permissions for a file.
*
* @param filePath The path of the file.
* @throws IOException if an I/O error occurs.
*/
public static void setSecureFilePermissions(Path filePath) throws IOException {
Set<PosixFilePermission> perms = PosixFilePermissions.fromString("rw-r-----");
Files.setPosixFilePermissions(filePath, perms);
}

/**
* Example usage of setting secure file permissions.
*
* @param args Command line arguments.
*/
public static void main(String[] args) {
Path filePath = Paths.get("/var/www/uploads/example.txt");
try {
setSecureFilePermissions(filePath);
} catch (IOException e) {
e.printStackTrace();
}
}
}


Dosya izinlerini kontrol edin

Dosya işlemlerini gerçekleştirmeden önce dosya veya dizinin doğru izinlere sahip olup olmadığını kontrol etmek önemlidir. Bunu nasıl yapabileceğiniz aşağıda açıklanmıştır:


import java.nio.file.*;
import java.nio.file.attribute.PosixFilePermissions;
import java.nio.file.attribute.PosixFilePermission;
import java.io.IOException;
import java.util.Set;

public class SecureFileHandler {

/**
* Checks if a file has the required permissions.
*
* @param filePath The path of the file.
* @param requiredPerms The required permissions.
* @return True if the file has the required permissions, false otherwise.
* @throws IOException if an I/O error occurs.
*/
public static boolean hasRequiredPermissions(Path filePath,
Set<PosixFilePermission> requiredPerms)
throws IOException {
Set<PosixFilePermission> perms = Files.getPosixFilePermissions(filePath);
return perms.containsAll(requiredPerms);
}

/**
* Example usage of checking file permissions.
*
* @param args Command line arguments.
*/
public static void main(String[] args) {
Path filePath = Paths.get("/var/www/uploads/example.txt");
Set<PosixFilePermission> requiredPerms = PosixFilePermissions.fromString("rw-r-----");
try {
if (hasRequiredPermissions(filePath, requiredPerms)) {
System.out.println("File has the required permissions.");
} else {
System.out.println("File does not have the required permissions.");
}
} catch (IOException e) {
e.printStackTrace();
}
}
}


Pratik güvenlik hususları


Yazma erişimini kısıtlayın: Yazma erişiminin gerekli kullanıcılar ve işlemlerle sınırlı olduğundan emin olun. Bu, yetkisiz değişiklik riskini en aza indirir.

Okuma erişimi: Yetkisiz değişiklikleri önlemek için değiştirilmemesi gereken dosyalara salt okunur izinler ayarlayın.

Yürütme izinleri: Yürütme izinleri verirken dikkatli olun. Yalnızca gerekli kullanıcılara yürütme izinleri verin ve komut dosyalarının veya yürütülebilir dosyaların güvenli olduğundan emin olun.

Sahip ve grup izinleri: Uygun sahip ve grup izinlerini ayarlayın. Hassas dosya ve dizinlerin doğru kullanıcı ve gruba ait olduğundan emin olun.

Sembolik bağlantılar: Mümkünse sembolik bağlantıları takip etmekten kaçının. Saldırganların sembolik bağlantı saldırılarını kullanarak güvenlik kontrollerini atlamasını engellemenin tek yolu budur.

Umask'ı kullanma: Değeri yapılandırma umaskYeni oluşturulan dosyalar ve dizinler için varsayılan izin ayarlarını kontrol etmek için. Bu temel güvenliği sağlar.

Dersleri kullanma Path VE Files Java NIO'nun kullanılması ve doğru izin ayarları, dosya ve dizin işlemlerinin güvenliğini önemli ölçüde artırabilir. Bu uygulamalar, yetkisiz erişim ve değişiklik riskinin azaltılmasına ve uygulamaların olası Yol Geçişi (CWE-22) güvenlik açıklarından korunmasına yardımcı olur.

Sembolik bağlantılara güvenli bir şekilde davranın

Sembolik bağlantıların (sembolik bağlantılar) güvenli bir şekilde yönetilmesi, erişim kontrolü atlama ve yol geçiş saldırıları gibi potansiyel güvenlik risklerinin önlenmesi açısından kritik öneme sahiptir. Saldırganlar, dosyalara ve dizinlere yetkisiz erişim elde etmek için sembolik bağlantılardan yararlanabilir. Aşağıdaki en iyi uygulamalar, Yeni G/Ç (NIO) API'sini kullanarak Java'da sembolik bağlantıların güvenli bir şekilde işlenmesini sağlamanıza yardımcı olacaktır:

Sembolik bağlantıları takip etmekten kaçının: Seçeneği kullanın NOFOLLOW_LINKSDosya işlemlerini gerçekleştirirken sembolik bağlantıların izlenmesini önlemek için. Bu, işlemlerin hedef dosya veya dizinde değil, sembolik bağlantı üzerinde gerçekleştirilmesini sağlar.

Sembolik bağlantıların hedefini doğrulayın: Uygulamanızın sembolik bağlantıları takip etmesi gerekiyorsa sembolik bağlantının hedefini doğrulayarak geçerli bir konuma işaret ettiğinden emin olun.

Sembolik bağlantıları arayın: Bir yolun sembolik bir bağlantı olup olmadığını açıkça kontrol edin ve ona göre davranın.

Örnek 1: Java NIO ile dosya işlemlerinde sembolik bağlantıların izlenmesinden nasıl kaçınılır:


import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.io.IOException;

public class SecureSymlinkHandler {

/**
* Checks if the given path is a symbolic link.
*
* @param path The path to check.
* @return True if the path is a symbolic link, false otherwise.
* @throws IOException if an I/O error occurs.
*/
public static boolean isSymlink(Path path) throws IOException {
return Files.isSymbolicLink(path);
}

/**
* Safely deletes a file without following symbolic links.
*
* @param path The path to the file to delete.
* @throws IOException if an I/O error occurs.
*/
public static void safeDelete(Path path) throws IOException {
if (isSymlink(path)) {
throw new SecurityException("Refusing to delete symbolic link: " + path);
}
Files.delete(path);
}

/**
* Safely reads a file's attributes without following symbolic links.
*
* @param path The path to the file.
* @return The file's attributes.
* @throws IOException if an I/O error occurs.
*/
public static BasicFileAttributes safeReadAttributes(Path path) throws IOException {
return Files.readAttributes(path, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
}

public static void main(String[] args) {
Path path = Paths.get("/var/www/uploads/example.txt");
try {
if (isSymlink(path)) {
System.out.println("Path is a symbolic link.");
} else {
System.out.println("Path is not a symbolic link.");
BasicFileAttributes attrs = safeReadAttributes(path);
System.out.println("File size: " + attrs.size());
safeDelete(path);
System.out.println("File deleted safely.");
}
} catch (IOException | SecurityException e) {
e.printStackTrace();
}
}
}


Örnek 2: Sembolik bağlantıların hedefini doğrulayın. Uygulamanızın sembolik bağlantıları takip etmesi gerekiyorsa, kabul edilebilir (güvenli) bir konuma işaret ettiğinden emin olmak için hedeflerini doğrulayın:


import java.nio.file.*;
import java.io.IOException;

public class SecureSymlinkHandler {

/**
* Validates that the symlink's target is within the allowed base directory.
*
* @param symlink The symbolic link to validate.
* @param baseDir The allowed base directory.
* @throws IOException if an I/O error occurs or if validation fails.
*/
public static void validateSymlinkTarget(Path symlink, Path baseDir) throws IOException {
if (!Files.isSymbolicLink(symlink)) {
throw new IllegalArgumentException("Path is not a symbolic link: " + symlink);
}
Path target = Files.readSymbolicLink(symlink).normalize();
Path resolvedTarget = baseDir.resolve(target).normalize();

if (!resolvedTarget.startsWith(baseDir)) {
throw new SecurityException("Invalid symlink target: " + resolvedTarget);
}
}

public static void main(String[] args) {
Path symlink = Paths.get("/var/www/uploads/symlink");
Path baseDir = Paths.get("/var/www/uploads").normalize();
try {
validateSymlinkTarget(symlink, baseDir);
System.out.println("Symlink target is valid and within the allowed base directory.");
} catch (IOException | SecurityException e) {
e.printStackTrace();
}
}
}


Yalnızca güvenilir kullanıcıların sembolik bağlantılar oluşturmasına izin ver. Bu, sembolik bağlantıların kötü amaçlarla kullanılması riskini en aza indirir. Yetkisiz konumlara işaret etmediklerinden emin olmak için uygulamanızdaki sembolik bağlantıları periyodik olarak kontrol edin. Sembolik bağlantıları anlayan ve bunları güvenli bir şekilde işleyen kitaplıkları kullanın. Bu, sembolik bağlantının istenmeyen sonuçlara yol açma riskini azaltmaya yardımcı olabilir. Potansiyel sembolik bağlantı güvenlik açıklarının etkisini en aza indirmek için uygulamanızın gerekli minimum izinlerle çalıştığından emin olun. Sembolik bağlantı saldırılarına karşı koruma sağlamak için birden fazla güvenlik kontrolü katmanı kullanın. Buna dosya sistemi izinleri, uygulama düzeyindeki kontroller ve düzenli izleme dahildir.

Kullanıcı girişini iyice doğrulayın


Kullanıcı girişinin titizlikle doğrulanması, bir uygulamanın güvenliğini ve bütünlüğünü sağlamak açısından kritik öneme sahiptir. Doğru giriş doğrulama, yol geçişi (CWE-22), SQL enjeksiyonu, siteler arası komut dosyası çalıştırma (XSS) ve daha fazlası dahil olmak üzere çeşitli saldırıların önlenmesine yardımcı olur. Aşağıdaki en iyi uygulamalar ve teknikler, Java uygulamalarında kullanıcı girişi doğrulamasını sıkı bir şekilde uygulamanıza yardımcı olacaktır:

  • Beyaz listeyi doğrula: Yalnızca önceden tanımlanmış kabul edilebilir değerler kümesiyle eşleşen girişlere izin verir. Bu, doğrulamanın en güvenli şeklidir.
  • Kara liste doğrulaması: Tehlikeli karakterler veya desenler içerdiği bilinen girişi reddedin. Bu yaklaşım beyaz listeye almaktan daha az güvenlidir ancak ek bir önlem olarak kullanılabilir.
  • Uzunluk kontrolleri: Girişlerinizin beklenen uzunluk sınırları dahilinde olduğundan emin olun. Bu, arabellek taşmalarını ve Hizmet Reddi (DoS) saldırılarını önler.
  • Veri türü kontrolleri: Girişlerin beklenen veri türüyle (ör. tamsayılar, tarihler) eşleştiğinden emin olun.
  • Kodlama ve kaçış: Çeşitli bağlamlarda (ör. HTML, SQL) enjeksiyon saldırılarını önlemek için girişi kodlama ve kaçış.
  • Kanonikleştirme: Doğrulamadan önce girişi standart bir formata dönüştürür. Bu, girdilerin güvenli bir şekilde karşılaştırılmasına ve işlenmesine yardımcı olur.
  • İzin verilen karakterleri kısıtla: İzin verilen karakterlerin beyaz listesine göre dosya adlarını ve yolları doğrulayın. Yol yapısını değiştirebilecek karakterler içeren tüm girişleri reddedin (ör. …, /, ).
  • Yolu temel dizine göre kontrol edin: Çözümlenen ve normalleştirilen yolun beklenen temel dizinle başladığından emin olun.
Aşağıda, özellikle dosya yolları için kullanıcı girişini doğrulamak amacıyla bu ilkeleri ve teknikleri birleştiren Java'daki örnek bir uygulama verilmiştir:


import java.nio.file.*;
import java.util.Set;
import java.util.HashSet;
import java.util.logging.Logger;
import java.io.IOException;
import java.util.regex.Pattern;

public class SecureInputValidator {

private static final Logger logger = Logger.getLogger(SecureInputValidator.class.getName());
private static final Set<String> ALLOWED_EXTENSIONS = new HashSet<>();

static {
ALLOWED_EXTENSIONS.add(".txt");
ALLOWED_EXTENSIONS.add(".jpg");
ALLOWED_EXTENSIONS.add(".png");
ALLOWED_EXTENSIONS.add(".pdf");
}

/**
* Validates the user-provided file name against a whitelist of allowed characters and extensions.
*
* @param fileName The user-provided file name.
* @throws IllegalArgumentException if the file name is invalid.
*/
public static void validateFileName(String fileName) throws IllegalArgumentException {
if (fileName == null || fileName.isEmpty()) {
throw new IllegalArgumentException("File name cannot be null or empty.");
}

// Check for invalid characters
Pattern pattern = Pattern.compile("[^a-zA-Z0-9._-]");
if (pattern.matcher(fileName).find()) {
throw new IllegalArgumentException("File name contains invalid characters.");
}

// Check for allowed file extensions
boolean validExtension = ALLOWED_EXTENSIONS.stream().anyMatch(fileName::endsWith);
if (!validExtension) {
throw new IllegalArgumentException("File extension is not allowed.");
}
}

/**
* Validates the user-provided path to ensure it stays within the base directory.
*
* @param baseDir The base directory.
* @param userInput The user-provided input.
* @return The validated and normalized path.
* @throws SecurityException if a path traversal attempt is detected.
* @throws IllegalArgumentException if the file name is invalid.
* @throws IOException if an I/O error occurs.
*/
public static Path getSecureFilePath(String baseDir, String userInput) throws SecurityException, IllegalArgumentException, IOException {
validateFileName(userInput);

// Normalize the base directory
Path basePath = Paths.get(baseDir).normalize();

// Resolve the user input against the base directory and normalize the result
Path resolvedPath = basePath.resolve(userInput).normalize();

// Validate that the resolved path starts with the base directory
if (!resolvedPath.startsWith(basePath)) {
logSuspiciousActivity(userInput);
throw new SecurityException("Invalid file path: path traversal attempt detected.");
}

return resolvedPath;
}

/**
* Logs suspicious activity for further analysis.
*
* @param userInput The suspicious user input.
*/
private static void logSuspiciousActivity(String userInput) {
logger.warning("Suspicious file access attempt: " + userInput);
}

/**
* Example usage of the secure file path validation.
*
* @param args Command line arguments.
*/
public static void main(String[] args) {
String baseDir = "/var/www/uploads";
String userInput = "example.txt";
try {
Path filePath = getSecureFilePath(baseDir, userInput);
System.out.println("Validated file path: " + filePath);
} catch (SecurityException | IllegalArgumentException | IOException e) {
e.printStackTrace();
}
}
}
 
Üst