Teknik Makale

Delphi PDF İmzalamada PKCS#12 Güvenliği

Bir PDF'i imzaladığınızda, genellikle imzalama anahtarını kontrol ettiğiniz bir şey olarak düşünürsünüz. Seçtiğiniz bir şifreyle korunan, oluşturduğunuz bir .pfx dosyasında yaşar. Bu dosyayı okuyan kod, bir sınır değil, tesisat gibi hissettirir. Sertifika sizin olmaktan çıktığı an bu sezgi yanlıştır. Kullanıcının herhangi bir .pfx seçmesine izin veren bir masaüstü aracı, yüklenen bir kimlik bilgisini kabul eden bir sunucu, ağ üzerinden sertifikalarla beslenen bir toplu imzalayıcı; hepsi tek bir imza baytı üretilmeden önce saldırgandan etkilenen baytları bir ayrıştırıcıya teslim eder. Bir PKCS#12 okuyucusu, tıpkı bir görüntü çözücü veya yazı tipi yükleyici gibi bir saldırı yüzeyidir

Bu yazı, her PKCS#12 okuyucusunun kendini koruması gereken iki tuzağı — ikisi de imzalama kimlik bilgisini içe aktaran yolda yaşayan iki tuzak — ve HotPDF'in bunları nasıl kapattığını ele alıyor. İkisi de egzotik değildir. İkisi de, sabit genişlikte tamsayıları olan bir dilde yazılan hemen her binary ayrıştırıcının vurduğu aynı kök nedenden çıkar: dosyadan gelen bir uzunluğun ya da sayının, olması gerekenden bir adım daha fazla güvenilmesi. Biri sınırların dışına bir okumaya, öteki, öldürene dek asılı kalan bir sürece yol açar

HotPDF diagram of the PBKDF2 iteration count trap: an unbounded forged four-billion round count would pin the CPU, while a lower and upper bound in HPDFCrypt rejects it
yüz milyon, her meşru dosyanın üzerinde durur ve en kötü durumu sınırlar — önce geniş çöz, sonra sınırla, sonra kullan
HotPDF: Two lanes of the ASN.1 length wrap trap: a forged length added in 32 bits slips past a naive bounds check, while HPDFASN1ParseNode reads into Int64 and rejects it
dar bir koruma, gerçek değerin kırpılmış hayaletine karşı çalışır; önce geniş okumak, uydurma uzunluğu herhangi bir SetLength'ten önce görünür kılar

Baytların izlediği yol

Bir belgeyi imzalamak üzere bir .pfx dosyasını içe aktarmak tek bir işlem değil, kısa bir boru hattıdır ve her aşama bir saldırganın yazmış olabileceği bir şeyi ayrıştırır. Kapsayıcı, RFC 7292'de tanımlandığı gibi, özel anahtarı tutan şifreli bir korumanın etrafına sarılmış bir AuthenticatedSafe torbaları yuvası olan bir PKCS#12 yapısıdır. Bunu okumak, ASN.1'i dolaşmak, şifreden bir anahtar türetmek, şifreyi çözmek ve ardından kurtarılan RSA anahtarını imzayı oluşturan koda teslim etmek anlamına gelir

HotPDF'te bu aşamalar ayrı birimlere denk gelir. PKCS#12 konteyner mantığı HPDFPFX içinde yaşar. Dokunduğu her tag, uzunluk ve değer, HPDFASN1'deki ASN.1 okuyucu tarafından çözülür. Anahtar türetme ve PBES2 çözümlemesi, PBKDF2HMACSHA256 ile birlikte HPDFCrypt içinde durur. Anahtar ele geçirildiğinde, HPDFRSA ve HPDFCMS'teki CMS SignedData kurucusu onu, PDF'e gömülen detached imzaya çevirir. Bütün zinciri süren genel giriş noktası tek bir çağrıdır

Pipeline of .pfx import in HotPDF: attacker-influenced bytes pass through HPDFPFX and HPDFASN1 before HPDFCrypt key derivation and HPDFRSA/HPDFCMS signing
bir PKCS#12 okuyucusu saldırı yüzeyidir — iki ayrıştırıcı özensizse, aşağı akıştaki kriptografinin hiçbir önemi kalmaz
// Tam iş hattını çalıştırır: yer tutucu PDF'i yükle, PFX'i ayrıştır,
// anahtarı türet, CMS SignedData oluştur, imzalı çıktıyı yaz.
if THotPDF.SignPDFWithPFX('Prepared.pdf', 'Signed.pdf',
     'signer.pfx', 'p@ssw0rd') then
  // imza gömülü
else
  // imzalama tamamlanmadı
;

Herhangi bir kriptografi gerçekleşmeden önce signer.pfx dosyasının her baytı HPDFASN1 ve HPDFPFX üzerinden akar. Bu iki birim dosyanın iddia ettiği şeyler konusunda dikkatli olmazsa, akış yönündeki kriptografinin önem kazanma şansı asla olmaz

Tuzak bir: korumayı aşan ASN.1 uzunluğu

DER ve BER'deki ASN.1, her öğeyi bir etiket, bir uzunluk ve o kadar içerik baytı olarak kodlar. Uzunluk, güvenmeniz ancak doğrulamanız gereken alandır, çünkü ayrıştırıcıya ne kadar okuyacağını söyler ve dosyayı üreten her kimse onun tarafından yazılmıştır. X.690 §8.1.3 iki kodlama tanımlar. Kısa biçim, 0 ila 127 arasındaki bir uzunluğu tek bir bayta paketler. Daha büyük her şey için kullanılan uzun biçim, düşük yedi biti takip eden uzunluk baytlarının sayısını veren bir öncü bayt harcar, ardından o kadar büyük-endian baytı gerçek değeri taşır. Dört uzunluk baytı bu nedenle dört gigabata yaklaşan bir içerik boyutu bildirebilir

Böyle bir değeri çözdükten sonra, ayrıştırıcı güvenmeden önce içeriğin gerçekten arabelleğe sığıp sığmadığını kontrol etmek zorundadır. Doğal kontrol, mevcut konum artı içerik uzunluğunun verilerin sonunu aşmadığını doğrulamaktır. Konum, içerik uzunluğu ve toplamın tümü 32 bitlik işaretli tamsayılarda tutulacak şekilde bariz bir şekilde yazıldığında, bu koruma bozulur:

// Tuzak: işaretli 32 bit aritmetik. ContentLen MaxInt'e yakınken,
// Pos + ContentLen NEGATİF bir değere taşar, bu yüzden karşılaştırma
// yanlıştır ve sahte ~2 GB uzunluk doğrudan geçer.
if Pos + ContentLen > Total then
  raise EHPDFASN1Error.Create('content overruns buffer');

Sorun karşılaştırma değil, toplamadır. ContentLen değeri MaxInt (2147483647) değerine yakın olduğunda, Pos + ContentLen işaretli 32 bitlik aralığı taşır ve negatif bir sayıya döner. Negatif bir toplam asla Total değerinden büyük değildir, bu nedenle koruma her şeyin yolunda olduğunu bildirir ve ayrıştırıcının arabellekte bulunmayan yaklaşık iki gigabaytlık bir içerik uzunluğuyla devam etmesine izin verir. Bundan sonra meydana gelen şey hasardır: okuyucu bu iddia edilen uzunluk için bir arabellek ayırır ve içine kopyalar; bir SetLength ve ardından kaynaktan okuyan bir Move. Kaynakta yalnızca birkaç yüz bayt kalmıştır, bu nedenle kopyalama girdinin sonunun çok ilerisini okur; bu, en iyi ihtimalle çöken ve en kötü ihtimalle bitişik işlem belleğini ayrıştırmaya sızdıran sınırların dışında bir okumadır

Tek doğru koruma, ara toplamı karşılaştırmadan önce genişletmektir; böylece toplama, hesaplandığı türde taşamaz. HotPDF her iki işleneni Int64'e yükseltir:

// Doğru: toplamadan önce her iki işlenen Int64'e genişletilir, böylece toplam
// taşamaz. Sahte bir 2 GB uzunluk artık sınır kontrolünde başarısız olur.
if ContentLen < 0 then
  raise EHPDFASN1Error.Create('negative content length after decoding.');
if Int64(Pos) + Int64(ContentLen) > Int64(Total) then
  raise EHPDFASN1Error.Create('content overruns buffer');

Bir Int64, iki 32 bitlik değerin toplamını kayıpsız tutar; karşılaştırma gerçek sayıyı görür ve uydurma uzunluğu reddeder. ContentLen üzerindeki ayrı negatif-olmaz denetimi, çözülen bir değerin kendi başına negatif düştüğü eş vakayı kapatır. HotPDF'te bu koruma HPDFASN1ParseNode içinde yaşar; öbür yardımcıların üzerine kurulduğu düğümü üreten işlev odur. HPDFASN1Content, SetLength ve Move boyutlarını doğrudan düğümün içerik uzunluğundan aldığı için, zayıf bir korumadan geçmiş bir düğüm, kendisinden yapılan her okumayı zehirlerdi. Sınırı çözüm noktasında dayatmak, üzerindeki yardımcıları güvenli kılan şeydir

Tuzak iki: silah gibi kullanılan PBKDF2 tur sayısı

İkinci tuzak bir bellek hatası değildir; dosyanın, CPU'nuzun ne kadar zorlanacağını söylemesidir. PKCS#12, anahtar malzemesini PKCS#5'ten gelen şifre tabanlı şema PBES2 ile korur; RFC 8018'de tanımlıdır. PBES2 bir anahtar türetme işlevi — burada HMAC-SHA-256'lı PBKDF2 — ve ardından bir şifre, burada AES-256-CBC çalıştırır. PBKDF2 bir tur sayısı alır ve o sayı dosyada taşınan bir parametredir. Bütün amacı yavaş olmasıdır: çok tur, her şifre denemesinin daha pahalı demektir ve çevrimdışı saldırgana karşı iyidir. RFC 8018 §4.2, daha büyük sayının güvenlik için daha iyi olduğunu açıkça söyler ve bilinçli olarak üst sınır koymaz

Uyumluluk, dosyayı siz ürettiğinizde sorun değildir. Saldırgan ürettiğinde silahtır. Tur sayısı saldırgan kontrollü bir iş çarpanıdır ve saldırgan kontrollü iş çarpanı, algoritmik karmaşıklık kaynaklı bir hizmet reddidir. Uydurma bir .pfx, milyarlar mertebesinde bir tur sayısı kodlayabilir; sayısını özenle okuyup PBKDF2'yi o kadar HMAC-SHA-256 turu için çağıran bir ayrıştırıcı, tek bir verilmiş dosyayla süreci dakikalarca ya da saatlerce dönmeyecek bir döngünün içine gömer. İstek başına bir kimlik bilgisi işleyen bir imzalama sunucusunda, tek bir özenle hazırlanmış yükleme bir işçiyi durdurur

Sayı, CPU'yucu döndürmeden önce taşmayı daha da kötüleştirir. Tur değeri dosyada sabit genişliği olmayan bir ASN.1 INTEGER olarak yaşar; buna karşılık PBKDF2'nin en sonunda tükettiği alan 32 bitlik bir Integer'dır. INTEGER'ı doğrudan o alana çözerseniz büyük bir değer kırpılır ve işaret bitine denk gelecek şekilde kurgulanmış bir değer, negatif ya da ilgisiz küçük bir sayı olarak geri gelir; o zaman çalışın büyüklüğü bile dosyanın istediğine görünenden farklı olur. HotPDF değeri tam genişlikte okur ve daraltmadan önce sınırlar:

// Yineleme sayısını önce Int64 olarak oku, sonra makul bir aralığa sıkıştır
// PBKDF2'nin kullandığı 32 bit Iterations alanına daraltılmadan ÖNCE.
LIter := HPDFASN1ToInteger(Data, Node);          // Int64 döndürür
if (LIter < 1) or (LIter > 100000000) then
  raise EHPDFPFXError.CreateFmt(
    'PBKDF2 iteration count %d is outside the accepted range 1..100000000',
    [LIter]);
Iterations := Integer(LIter);                    // güvenli: zaten sınırlanmış

Bir Int64 içine okumak, çözülen değerin kırpılmış bir hayaleti değil, gerçeği olduğu anlamına gelir. Alt sınır, bir anahtar türetme için anlamsız olan sıfır ve negatif sayıları reddeder. Üst sınır olan yüz milyon, günümüzde on binlerce ila yüz binlerce yineleme kullanan yasal herhangi bir PKCS#12 dosyasının çok üzerindedir ve en kötü durumu sınırlı, hayatta kalınabilir miktarda bir işle sınırlar. Değer ancak bu bandı geçtikten sonra 32 bitlik alana daraltılır, böylece kırpılma artık kimseyi şaşırtamaz. HotPDF'te bu sınırlama, PBKDF2 parametrelerinin PBKDF2HMACSHA256 yolunda çözüldüğü ParsePBES2Params içinde yaşar

İki koruma neden aynı korumadır

İki tuzak farklı görünür — biri tampon aşımı, öteki asılı bir süreç — ama aynı hatadır. Her ikisinde de, güvensiz bir dosyadan gelen bir sayı, gerçeğe karşı denetlenmeden, bir adım fazla erken sabit genişlikli bir türe taşınır. Uzunluk, sınır testinden önce 32 bitle toplanır; tur sayısı, aralık testinden önce 32 bite daraltılır. İkisi de aynı disipline boyun eğer: tam genişlikte çöz, gerçek sınıra karşı denetle, ancak sonra daralt. Ara Int64 bir üslup tercihi değildir; saldırganın gerçekten yazdığı değeri korumanın görebildiği tek genişlik odur. Taşan bir sınır sınır değildir ve üst sınırı olmayan bir sayı parametre değildir; kendi CPU'nuz üzerinde uzaktan bir gaz koludur

İmzalama boru hattı için pratik kılavuz

Çıkarılması gereken ders, güvenilmeyen sertifika girdilerini, güvenilmeyen herhangi bir yüklemeyi doğrulayacağınız şekilde doğrulamaktır. Kabul ettiğiniz bir .pfx dosyasının boyutunu sınırlayın, çünkü yasal bir dosya megabaytlarca değil, kilobaytlardır. Ayrıştırma hatasını, kullanıcıya bir yığın izlemesi göstermeye değer bir hata olarak değil, rutin olarak reddedilen bir girdi olarak ele alın. Bir sunucuda imzalama yapıyorsanız, içe aktarma işlemini durdurulan bir çalışanın hizmeti beraberinde çökertemeyeceği bir yerde çalıştırın ve işlemin etrafına bir zaman aşımı koyun, böylece beklenmedik derecede pahalı bir dosya, yineleme sınırının yanı sıra duvar saatiyle de sınırlanır

Daha geniş kapsamlı ders sertifikaların ötesine geçer. Ayrıştırma güçlendirmesi, bir birimin tek seferlik denetimi değildir; kitaplığınızın yazmadığı baytları okuduğu her yerin bir özelliğidir. Bir PDF kitaplığı, güvenilmeyen kaynaklardan çok şey ayrıştırır: bir belgeye gömülü yazı tipleri, yarım düzine codec bileşenindeki görüntüler, akış filtreleri ve imzalama yolunda sertifikalar. Bunların her biri saldırı yüzeyidir ve her biri her uzunluk ve her sayı hakkında aynı şüpheyi hak eder. HotPDF, içe aktarma ve imzalama yolunu burada açıklanan güçlendirilmiş HPDFASN1, HPDFPFX, HPDFCrypt ve HPDFCMS birimleri üzerine inşa eder; böylece ona teslim ettiğiniz kimlik bilgisi, nereden gelirse gelsin, güvenilmeden önce savunma amaçlı olarak ayrıştırılır

Bu kontrollerin koruduğu imzalama iş akışı, Delphi'de PAdES dijital imzaları kılavuzumuzda uçtan uca ele alınmıştır ve bu kod tabanını paylaşan AES-256 anahtar yolu dahil olmak üzere belge şifrelemesine uygulanan aynı savunma duruşu, AES-256 şifreleme ve güvenlik hakkındaki makalede açıklanmıştır. Tüm bunlar, bu blogun başka yerlerinde ele alınan yükleme, düzenleme, şifreleme ve imzalama API'lerinin yanı sıra Delphi ve C++Builder için HotPDF Bileşeni'nin bir parçası olarak sunulmaktadır