From d6ee953f529a4f0f04bbbb31a3c7fc36de8056d5 Mon Sep 17 00:00:00 2001
From: Sim4n6 Security checks bypass due to a Unicode transformation
+ If ever a unicode tranformation is performed after some security checks or logical
+ validation, the
+ latter could be bypassed due to a potential Unicode characters collision.
+ The validation of concern are any character escaping, any regex validation or any string
+ verification.
+ Perform a Unicode normalization before the logical validation. The following example showcases the bypass of all checks performed by For instance: the character U+FE64 (
+
+ html.EscapeString() due to a post-unicode normalization.﹤) is not filtered-out by the flask
+ escape function. But due to the Unicode normalization, the character is transformed and
+ would become U+003C ( < ).jGUr{^b|J1fOGU-|@s&cSo^W0AUf
zD%
Perform a Unicode normalization before the logical validation.
diff --git a/go/ql/src/experimental/CWE-176/vulnerability-flow.png b/go/ql/src/experimental/CWE-176/vulnerability-flow.png deleted file mode 100644 index e1a354717ef1273043075e966df13a19e4402e1c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 37706 zcmeFZcTkkivo9>LjGUr{^b|J1fOGU-|@s&cSo^W0AUf zD%DmAD%ldbN_dxBt<_+n(yQ*YEB`JYBFW_QB6%Up9bCqn+1C|R9zYmq@aoP@l z{^+;cFIjo# y}uhUI(5~l2dRS@aT9nNj{oK8Gm@B+Eqrml7@KroRb;uwnbv{f{NQ} zvevoMSXHs+WYg15g6GW@<1(e?S#{Q4@x5yjkDL$gd-g^@&grM%0;~QqKUVtI{^s=H z9^d;Y9>PIASuQMTa}8{MB(N2@(Jk!pNDg2txDq|qH{ZOIAg`J9tD+7@%D64ZpYHUD zRNC}29vQ8{l{l#ka=g`cN{0Q5i#S O>2c~c2lz8=&4g$R=m}!5zT=_EquZ<1 z49(b|PE;op-!+3z%sRfjX6VtiJ>@ywyO3%w{Pb^q(%(AkzSPG)WU8~4cPIS{+c1hO zY_H_Nj#>Xet9}cZQ(*LTGS;JDf^g)vMR0 +c=^-OMRn8}*zP zieZvWQO6@q{84784zthhi#$ycGA;0rAoF10oyjlsA~?jJ*~8HUvZ*Lq>Ik_74jae5 z9KW63AtiefyRmmdWg`#S3Swn8f|1AHQU& HZs}H_%>OdB5cj^AhFT>GtQ{I@r^ZBDbJCrcgvdmWvoyC&T zw;ds+-d@_AMDLr9?TS8(${m>T=VENW=cx|7xW%^|0Jf3!PBOpxS3+CEdXGQ1p0+!= zsRMI1lL!Bg(`oGrWGA1_8NM~rn`zmctQ*$QAWB^f@uhcS!;xS6-B^Ev9gT&4uAw8( z53G_$7nkz{RVbdeost$-WZGc4fx6G$GP~>2z{|%uzQS`qcNY3&x*ItvXH{j>hEtr( zakTG>sh2A`&1CUaZcG+N%{++INa7*XPm(T;SIi2xP196o8Kd@*9;TARql+mPD%@w3 z$j|qxXxC=u{>K51at6Dc-x1F8`{USywGoLKD^41kaWyw+ajaK z`H-lzUyj_g=BX d+_9C>N8ny6AFzRY@uUgAGI z0~%i7a$xYJWgCV^K6FtQ-`(pmFxyGt)nV vAr4>X{zN*JFoIlE$9LNdD&gXgBJX zdEV%IAbp?=FMN`f-7)WocA9)wqiXo=368Kw`=9TFwlsPBQcH-MJDw{OcLUCkmP#;K z{v19|g^@Iq*8%O5CtDaz5vffN$;s _sl(HWeB=xDhzk zo?P*(U`G8ZC1y6laYbNk{#O~7QhjaN9Xs?oz<*_=uFBQGb7iQ|8WT!bxtAYjHBzGI z?EgpZV3N~tnRIrF%?o+cagyF(dV86(=&J82RcDC0u3t24C>k)D!H_)69{dD$ijLq= zjqIr|HE8$n-kMQ7I@xSqh+qp$_WT|2%elqDj`rHU Kv;0dL*cMqm10 zoL{?USRQ#|7Q(~2b@Hp$*)m Vjib~lkH=<2 z3wpNQ`p?8iG4C%l3g)|e4&OT3>)cmSajbIl+V|Ihq1ox^8C0er4|uX|Ood_t3%_Ei zIS>kyr9`XBI-Jhpul#QG9Y1G><0%}MF#E25h~+#V)=}_gRIg-fON)~~UPhwv2^y6n z$O!XPODA_q39^nRU3W%JpIP&>KFZPo^Oe{978o=4Wa(5E5b=qPfeC`6%OdwX!iuVg zEZ8dwKYC$6W-}T1pFJHKN%L8rzkBH)%(jIsgfltDPb_6}Lf&bh1PkO4?o2$#t^D#b z1@bah#&bhR7^Lq+(q7}~&$#(AvG5il2kWKz@MD=$ZOrv{m0BrCvg#hl+L^^?>me3t zO6z4G$Wdq(|FQYNZ8l85=r+Srm3iRV-QWJ3a+gjLGF8O 63Sq7(vp0(?7t`MQ zPlMHwcR~l%Pp^SJs9w~a?X#n?)V!T;yTtd3$M8NkZOLNY{a0_|HOkqGN8e2bwuNf-L#zV z-<@RN!0)P8d+!=P;J5BeabFMfJCl0ffQjy%5^k8m=H HPkBMj`( h^XgOi2i=&vvWLbvMvsjy z9%nm;&ktn%nw2;?T3S%LAM(JS^>i=T(;_DSLQG)t1KFpizj!oLg }hp=#AwpH$)d%-u}odiiQYMeg=Ti?5z#9w!D~9H|T3eZxiZfCAO% zbKsHbUrqSMqd9=>?CH WQCb>Htm|9W__T+UR-Pv5F!NSDY!BC0dWx{> ziI0|s5wyK}_F1Fp;@r18{Dho2{>|eoZVFZfJ5rL+Nasi0u?(C?Bdt@pj~1gO?B{m} z6;iR)S-5MKeFaM5eXYL1jT?PF&Jm2=3nst{cCSF?lbe2WNYHw9(^`s6!E&2}_4F;) z+rsak7d2A^SE2#WO-W+iEKBI{oWS#=i&5j`=eEuMlf|mje?KlWMQ<&|GggNai4oj; zn=5MBEzQ%lN$lV@0nWqa@A=k)zZ+vMB3|!Ls8 #sd@mzJ9^&Rw(Bt8 mFCA8Y=%m6HFi*K;tim+o&|Ky9!J_7u zI65K1eT)ga?N1N8oK2bZ=tc& {FvhZZl{&!%ZSkuY7daJIgL1_+RQZkwK=Z~D$Cw*-xyb<06ZMhdgSY)o^ z6|9nunGW}YhqQ37o-++9R>;2oB+7v`2(0&0mwt({o@(1+W-RngwQiS0qCDd&8!1YX z;p^%uoOET4Jj|c2Mvk+MEf2qX)|Afwlz+(YG`2I!YpV*6LbPocefl+9PR7!a!W|VA z@;ZhG+F*P*L-=~S@_rrY+cvm0M6?1u_XXxJj@Lw%s^kUu*ej6_1d%VSS;%2s3WDN&Bi z^7FJWso!O};|K3wQ9TQx)0TyHN>4yX2*a)gt@*fW> |f8%RgN6+VF@9d z^V6gB>>KJi0i`!dS^B0JC7jje;pn2}dZ#x-`KwRzRjJ4m7Uw(#A9RG|NPIIKyGcf} z{5Fr4lFBpWM% 5TQ!^du9ihP) zxhK3k3D=S|DO^#@C6(E#cU$^3;%*^`(s+J fME-qxL>R3oM(59nsL z)eh*uIYyix&SmWh7l^QXvOX; RlP8P` z_34`3lM2eQvWFR-aO>VI8@glR-OLw^VF6@{uaBf%x>-D}r0AM4?2}+SaRX-S@px7; zLi*dv>pU_NJy!}-)xg}x$ZDH{qHF9E;yU^A=!x8SxcFEzo|K+eKuf?DTFudmjCi}e zb%P3J4!Jb;rUTKiHY#+g7ji><$U2fvXtjXs>F)-qIe$dC&1WL40@~bgc9W gB zPLC&E_2A?&qdi`SMdCMG#)=oe?P$N@ s=Q2|(edZ`2>gk+BM1Gkh0)SmN!N7WrX;o&V0^&@Q7ll3jY$xROe2n*xaM zUkhujN~%i`=bn|-qx^)e==GU#`B-b`vyD-WwHV>1Lre*#^%)xt`S*Zdhf;*FIIyT& z4g(DMnaG9?oa@jf(i^FhWW2kOD&{oPa@&em;j9`?3e&b)=2vhUn#64bGj)9@s&S2F zu~>)IM|rf5${y0Y7Fb)Y(ocMRwn=04D*U<2h%J1GN<%j7vDPu!>h0_iOG6P5D5^7b zolquz(Z4&h-UJh*+qSS{%^ntQVx-b|o~1dX!Y9)KFz>{}00zepx LCkTxRuVg` z6^(}fz}pQPj?VKMc8bHv d~E+yY7N%2iZmCwVyZK|!8a;dlonQ_x-0ntr-^7umn!UAgG zLPyN)nVsmFe2t#toHb+Y9?eV*Y$N1FsD4Ll(~NQ$Pcj7=B+po2(zL6+(D-{HNm+I) zE0VSNSjAUc72g+olFu}peVY@HkUOt+t>*3^-H2@w%?s#aw$;ik4SS1Vq?7sX#P{73 zTr3vMrW(~Bx+%)N`4qD;nV-%GTOXNwhm+YEHyLnUAkXuWg?z0WNq?qbs1Jb(+{6CE zVW|L`f)MFLzj5yn+_TYlWoLYO3UNR3QE<>d^Oq|nI*^@F;ftmu?f!r J(Dl_vw(TG^jedQ6cU)!w0o{nd6?gb)^uG?;>kpDX<3CtS#$V-G zwLhlVXlc>7jWChW&muB9Ucvb&V^o78Rvvkr^MD{?E;_30wXb2A%qaE7QcD(3?Qknv z>XZbZ5#CSomnz^G92>=fXHr+G(B$CSdFW&%!ev1UT~BVG^2nXx^0xS$Zg-0*>BxzZ z#!`ByItSBwd^&jJdKyA^iXpq=`M@7+$jtj@aQs$XM# cCyZ!MOc`R?-MC_qv zTRPoq&9Up3yV7^X7T=P@%J9yNaKzxpN_4b}3vsDg!LH7K9Ad;1FL?Dj1#3__l;!U0 zpt #~kqaHz&65^Q-#JZ|wK5nyEL$^HBC(I&3KR_D(M&Q;MP-K2DmU{8=|{f?H@= zW|AP^>+lC(N@JWZh&IB5=>LM_XDt(Vc=WVtFgOuu24O3-yfU1@BfK-}vIXZ32qh|b zuoZMZ(|I?g!V;nQVcqGG2Nep!azZ(mh&A9sPza;YSc;zt4Wf$d0vz&4>=jk;)VSXJ zGagw91&yO*r!}#Axj|UQ?A%X=kPC`Dv?c6@amA-V?$XfPOd3KJ&`d!~$p#PJgCL^^ z`|}PcxG`u>RL$Px4xWcV4B^10EEp9O)|l-%Qldg>f|J75_MUbmEDtoJBhlpXNFRt? z5nq>^T=9aSxtC+`AQ9;Tg@RKRrnRvNq2Q3yydOJZ0XN{GTKpQXDz+YIR#EN1N5LBb zxdo|mQcP4RI?!yDu;i-&uY;)Q%X!8d90~ c_1>q}|J-dxu@n#Se3xhRD$To-yW+`(Nwk-sE3H5)`NI>2J&3csESX3wx z@U&vBGY}3-gyd&DfAoL~B@C#T-&y*l0iOkfQ^jVAa0OR`>v(*M%|ZbRE&!TK(1%wD z3$B8vy-ME4%GkY-`~-LWL r3;Er$)lFn+wDMwoOzAn&7>DWCP*HC=G0;n-1}E zzWb=w=eUJRVMP2=YGjd@fHhnSQquOUXWRg14o-&lT4m8}3xr(5SD+?GY_dxjTp!X* zyPqNX>`=!$kgnVsoiV!Xhzn_d5G4Y3Ih!6(8Gbu@vM6YR|5Qnc5P#5A`iwIcV8}r* zDrn#5) frH(H%Z4?*%B5Q==$ z@stkw01E~TQ@s5ul?X-;A_wEiTWsq=H2fz_4T<6j7~%5%3{4Ad+SZS9kF_B<{vqJP z=^+>jyi|a|T0SaXqV(}EAov{iuOxs@oUwo90tE4b-)3LmX1|<*3rmd^+tx!n1Cjs- zFds9kU}o^Ln1}Fgh^-cC;2P+p9a3CUn|OtjJXl bboR~zZwT&-&UJ7=k-fjRsl)4q=@+PLWHhCvEj#JWNFY4RFGgFmK4bk@Lov&9IiBFLqq7t04m-Xht6H<-w22u$4h^j zL0xv_hV(C#0YLx_540WVpDo?L6#Tz#1b5I>>c}1&cS#f!vu i5T^QX< zqA0=4REp`F5XV?DU>V14C1Wp_!UYNn$L3mhLmXSkffZQO>bYD=q#x+lk;?1K331{E z30He`-t3Y *c;nxduWKNL9-%zK``eK~XdD2(14Wxwm<0(FWP0!=^Lx{6r<0X 0GvSg?lfJaa*p z3_OI^Fg!!#GKWS6j5q#}H#QMk_(Xs#urrJtX+u9kJp_aEJ1~=7=KCZu9?}u4C@{Q{l_1&6OKpURqD(%z6nO;*w1#AP%#~b{@eUSn_Sf)d zlfQ3={Iz>;`EZG7&}%Y387@~j$R7;3Ud5n(8(L;(UT`_YGHwoNT%T^fKsW6!^zptF zxSI&_H9F54*DviZszn%Z;_)*o|I+3&b5>aQ(f$1N)U@cFo&HveAiwpO ~3zrY%3l_{{B&-PzAOntuV zvNRwRLB^6eRH%7lHJ)O}kMRGkH(DN`(l_&)W sYcA {dQvje4?|nG$?ZS%^i*9`hT4t@Zb^1c1-NgE~DVTV584|ak}0+<-jLr{pw-9 z4yT93zYG2Qe1to@f`;T(w`Gcv$k>>ao WuWigw zwFY(Rz4DA?;rk|#QeK>%yg&WqT{Sl|Q1jIzYomVf!|6D0c8?L(B_JqW{Icv`r~h5# zm78~*JvZu>Zq>Og6zJwERoagU|J*;>tn@o_@jjUH%(}Q4gPFZ}1+2nGUjj=a@)o3^ z8dXlZToek}A0qJ)w_XLF^fRC~% B~yOBZyr`o47-uTb })9=kU(ej8|0AMFm>&SlpFcor465wdhB bfx9yCiO@9z`|7U9mzGm3YLKY3_x>eN~TkstU^8cRs zbvsGM)3NTorFyC1`|Ph7a;A%4)02jZlsl}ZoBY+IDeavWLC)sNgKZje^mUS933CAK zc|UE%PM5I1S9B0!K3t@=@QF@n{(bX#qIQ-{-*n)G*7it&TG*|pBr1mq;3u7i|Y zh9h_c_=wv<3~k<@!J=B99&Yb!_-< aWkGkx9|Fr|pO<|$UT4lSD5_a5Jd6R&ng{$3qo-(@$E+aal z&ePu->M}ukkbbSx!^a0Fn$hM }i>+Pr`Ei-$G)SV8zWC#K^}btznft;^{Z9_YM~1@@04?Wpec;{s z@aaS}!wsuc>7OijDr$c@ceC)$b;tBoKloh287sdz>XQI6%w+8CiljG*M_zc2$) n$;wmI~O&w zQG4dmn&IcTc$@dwf`dF~*(dwv(~N4nAGZcZJ}l>@uq6{`6W=cmSdSL31^`^Iu}+u- z_D=n~<~W6Oqs4(&sR`2bwsP!$-<+Hs^kqocGk=If<&J%vt~20Oe{%ej3xojnnZ68a zz~z8;6hyE{5mTX2@ZXr+_eIx!jL^^{dKH*G_HuN;>*lEt+D;YqQ?6X+(GMOZvX$-+ zC8V}$EE;a93pCEMLzFsoIX3^f* H+3|50&M zz}c~OC~n}4Qu~WCgZh2SM}!!xmHRRh{kTWl<^e~A$-3R-o9c{|h&1Lir-9$L#aTQ! zY<}M;Nut~gSxB^HXJK_pOBo=gU}d=GS(h0ngYR-YyUS{6@vRFiocXo)&HJaOSoqZL zFbY*C@!dBB(&|e_21`U(Sh-Io{*K+}t$Bk`JRaWBk*7o~kuu$MTa;Frezv%*F6;IU z<2=I9P#k;W{CT(emOTSJ1I{;v!NPv$ktH2VA12pA=#DT_aPs(&ZDW6@bLx#fS5dQ? z)#6}|$nf27Dt1XD0oAjUIM7f4xIq+E=B55@0f!IONy1EZpuD Hu}Np(T)K!)9)ugK6ERnhD{e zK!B4e_=XfOvp8fnGhr}w0#Y)LoyN75jjh7e2dgww+Bx!aI14`*8Cqi3x#Sp@0E#nk z=*{oG$NR>p<=qK7finyH!Kox;=0ik8QLA?-wUp zc@FW6Nn9&GPWpWjgPtggg#mG)CTmecU;kkvT)ybda10GOjb(&A>RB5bqHLVsgDvjG z)b8$pjEG%E*w|8ABO3=%d3jMYas<8;jKa?V$eI4*_@WTAV^fI*V!e3X$K+vOQA;M3 zRxfV5z7V?+j&*iI%-r_t&TG|c`rX nEheoPqPgJlUEhxoIAo-q- ziHmr)ztBSbS;qY+=@5(7VTysvmD?gwk6k3uT!{2(|AVYuO9f0TqF1NXLRGwVssW8J z8|TJe@K#RjN&MSUzAKCze1lK){)!0L`5bJpcxcT=@QJHa-h@rG$H`J{*EzA#Z&%#S zfAr^0ZS5~rMs{Qe@gw@Y@%LGx9&Zosc}~47HE*ESBl~I(@}<*9sfVyA7nEp7+FDh% zzbSf*HeKbTA%DQP%|KO=11=h8H|I%Ea80l~A`B&p*^2+B-BZ~u*O6c0;$;lV`ucFy zfqiVM`b1)C861F~9*!SEqQbH7ps}l%96H(`Uo(^@(|+=*@MzZ8t$5hqu#J1QU{1%l z93S~6v7Y^|RlIc6lad$63_K6bS-!yBz^xkRjsAlM`G_Ua-%JvA!OOTFQwj3(jucWn zIV_mmw4&6|GRf; KJ>Abe{4K>aCF}wuR*-v@U;hq I1J<{!oejK369qb-T}0tF()@O|qGe zBYGB1W45gb=2qQpr{p3Z_)Gq3xvdr){i~g2$C5C^60hqQG0Caq@`vy|e@clU0|w0y z<)2FSv@Koz{VKP0>6!TDaO;2dWNns6z)A1O`jnP}oUmF>DVx{_ojBQS1$rTojLyM_ zDsSj^_VvW0mDb;Ga4Vkmlf)c4eE%CJ&Z{6)mT#im8Nw!$KY+#m$iCVG51-2F$L(gL z9HaM7fQL~nI3k_ *dt%Tt>}@)7`WEHSGEq~9-)LA0 zmf&|`GV^?I8y8YcGlxCmi$YT H3- znKNKLlu~(()q;B522-G+9fPH9ti-5uub4MoDth!u*v}7=_*kK@*!0J) V)4(uNA0#Ov7Q_D1LVhlodoD-hJQ9`CyE*GsIBeFBY~A zE>{*cX9ypFkidC6aI}$1M6gK9sR6=Rr%t_h {0+3uX}EZ z4B$e4lxHngCQqg$3mL0GR57LAqlS4jDm= #O(d46N1t;hZ%3~l$kkF7|AVJwQ!U*+%bYLyvE}Lk%FEla za>M9VGXH^w)bdqNtk>7C%CtLnj}% znzF}$z}eEyTj@ij%_PmAUaFKIzP&(gweil_a2+b zT@Y3CoaL*nW=VaJllXV|I4lpgqJ{WDKx)}Vc9vz*v#!msnme#w=hJ5J7L(iXd*!;B z4ug++zPJVJ#KtHFqvk_JISTKh^%+#*3ZP(}iEs}aZiS0a6+f8WwSQNq<~YPiuQK%Y z^bC#P@GCA$FO#g5&ZO$$hKb@ToBoyda^7g#ZnkD{erj`dt!)yF$}{SiVlGNSG~wT4 z+qT-^!WVRZx&K|%j|$=#@rbHDY!O5n=MU#r(S+Q7=FJzGu4v+ecg2+CXkm*3CR=HQ zC|;^x>ug9B-qJAQ>7pC`$hDd3A~BOa;>Sdi#M6b^{LF{^$IXUoj@uxEe3RM!MV4je zr;xU_M8?Jz7MQBr602(Qc*0G7vG3XKgtAV1GtqO2pZpm`kz~HkQ-A#piP7K57s;1M zEZbLfC}h1@Iq>SIoCKY(iMp&h^C;|$alXKJJy+mhdcWrUp3kE*3x6>iA6?+|yrrM5 zeyPO1<(<^;i+nY){SubzfoOU(waRt) J|Q;Gz=?626=73iYr@ERe7ao1r=i z?$#Aur#H@Z%p2awsZgmEg0MxWYG?0>HJ=2g$bOM~+s~tRDGFOWW4`SseG1jws^E+} z4H(HoR@AN;$NPlRaEUid+HxL;{f)} Z2$eA+WwR(;bR}kJsOtS`!C{x zhdF~CKt6U4o@~HNm;)SgsIr*!^=3+1TRXx`6PS%%+q7!M**RHt=}97XZcZ_jmyiif zn8?ks;SMhr@SKvwatOo9in`-i%Q3hE*FE+Vp2eMd_~EHv?VO??dubvVL&HpC&sI2S z*|1h^KG|hIRJPTSy0jI5s6!dFZYXq6FwdrERECVI@Am5ah?<%aoMXjXA3rZ_!-|y} zICx*A&BVN*<@16ikN;v==IZUf)o@#to4PvB>y*)OA>0GAw7#vpaw$Y{?Ty?>)-0mO z^x87Q1R?aTMwG#XTERq(4;jC6sW3X(GHn rWQXNfa Cp{Lmot?fCnGJcVukI3HJr z(f9|>89)8mnsu-pvC&j;qP@BYPKULT1fM!6E&7!)M!Q;E@wb7$(OWR+3BrQ}T>7#W z>a^Z-s8X*xBH_eOK?v@;SBv?>*d+I>Rrt&rZACiU@z+QEC<(cs5 FHz!-1(ib}xEPUV4Lsm~~u_Mc{ALQew<4rU4p-$wTJ3O)wc;&~Fo z{ae_j5RUUhunhzkGYPqrWC8w1 *dMm<=3Jpaq zqh}5!s4AfHTA>sSLZihIFz)dyE}s|cJqUR>Bw}2-Eck&3p-I~PFe%6yd?$E=K0*^) z6vFf=nVZ$HKS2cuo%+nYgyW#nr`7Sqd5>!b2;yJot006cmd+v^s44)c6Jm`w=cEBw z1Cxk8pBqE9(1E8 %;cFd?WAnofc_LJK mD zKv0*06A=&tbASpmq6!h(R285^H)LvmZds!R;8XbT3l5nHjUW>A+nvX`EEvFdeI#A8 ziO6U$$s<_z`-C7N@Z|7;rdtEs1BC1=eg!bg5qbd$&%C6`0hJ tSkLv}T z{HYO~c5B7GimG{c-+Hy$w$h^YIU?rPd?{~^+wte(r@w0d_QvJ`-9QhZb>flIU~i)E zuep$M#p43)UGAHMidws37&QQ1RR99(i69hwS!Gk46PN-5M9i~WSNZi-7$qGAp}IEz zlYLcx#lNd+@7HAhJs03dAt+*t4QT*{1SNyNc94i|ClI*S1nF$nP>Cq02x7iU-k%|n z1cF6hinLyd{r<7ibdq@?xBr6IK{1_fuD}aLe8Yw69|g~l6CVHZj8Xi_j0!FsTmEd% z;K+T~MyevPxkXg|VA6FV4U{=}ikn>5%f~CqGA)kXTmG)R;qc=YNP4tB!0zo@ZhwKV zFl8;dqg$fOo-XSDnS@E+EJeh8W9f(AO?=3-Ub*7|3Q!^Afg97to`94En? x z6=C<|O>fZo89%GORj{6H0h%2_Le&G|oKhrdQ%T55X#7ttAUr8DC;kz)R3(mtk!HUY zWT_Kmo7Q`jL+SovmR%(LcPcE39&bMRsSnC_WNke+8^sPkbk9cvkgiX-*{liFz%WR= zm4udgBe^UOHl}vsBa>2&&oU(JdcLc({UJ!J+g (|hZ4h mFHU267k zr9)C)Tdz-#*WN=2L2p|aah7*}x>REkzYP?jo^(F>^St_Ib&SF(9KcI&{VvW9de1*< z_Z}F?78DpG_^8k_K)-7_ts}VNO@NW)_?7=UGRW4T_=|al&q%qcWVc4lsBW{ jcTFQFOfkb||na)gM}pyvFPtLeM{GLX7dK&ZoVd zAJ~AuYkc0z``j&G^COzsUzw(kxQNM)(HMZd^WYBamgzZ?oqWwu`#Qet^q=z+7EuFG zvc2?pdFiF0xqF_{+Sq%~RY4` %RHG9RJ1WSwGqvC9d8}S)Nwl zPCbwbPbs>cEoeW&Q|R`4cX2S4?T$FJ=cKa>sOOgYd;O2QpQ5&5RP_MJzByZK{cq8G zu$R_4LB%a`-3CB+>ZP-ye!MU%Boog3_I4=dcyB`p`~kw3 Y+5mf(gza_ zDy(11<`m5AshZA@?Y*65kdM3BNC!@y$mJ5^t~zc9@!htL@gn(-jxqf@yVNi4GV|^k z@`IaZmgkdxWkBBP^}%lwFJ%Q9;9ubI&k9>X4wJQ_%CZ*GAj$eA02-~Y*RFIiEvTK{ zuvs7H u|GG8sE44KGS9DZPs`iWWlzLLP;$m8sU{tUH|`@=bH5?ifoDv~^ t@4-gNV|H;W!UaBS)yMm+{Wla#w;eXz{MBo8iOI zWPTMbILTDMiu41oZB=i*3;Ofs)14@{)k&%0s;;iVH*!uxbfz}I;+awYH-P+iOq|)p zjX{L6b~oEpOqYgcPgvmEpPH<;Re(JWt_WV+-U4<0ed|j(gL^|LOm1}cvfHjujg`-6 zCVVe!@lauJtDRv$aFrKN_Hc$WDgB)%*StQyOj!?3lNGbM=jsqJo+1bKpGV!wv(hdm z>>2zm8x9Tj=aaJTaC<6S*?gNlrVvSp@D;zmN~9&?Qv5LeoaAba7t+7uBO~FPGT!^S zH?brbHuf_| ;YK6RaSM;bZ4^sE8CVNN3Np=uobercc61*zY@}-*_B* z5Jnr%%g>7a=!y1?EnsA@n_&d-DPZS2Y#y$A<&^TPluL*Ou>{l%XS@XEFwqgxy&IYs z3Q@k&`XeF7dLhm2E5V|QB9BgP6Bppy3n@KedVj!AqnALfCEG$6sD?}7G2&}GK|6ty z@y5V(IKe4Bqb_NdPUwr{?}j=9`m^vNzT75EOEAv!q+(sk8AI9EWUx}*Gkg*!{nSa) z@1eq<>o@7hQJmNZ+?uIh^v|kXm$Z3*lztw)Hj`Xt1k(72HEsP-yG)LV=WMI|YEMjl z5Xlu??fjz#f{=oXnl$9b7s7h79sAEnn^mztK@NBSBW4l8L*Tm3P#g8kb$NH&D*IRO zUlB^c%#K^ez3k1{saot4v3@;D5_+?MWuf<# 670@Rt0J#VP@`+%U?!PiCtr%_M4U_>O^`4NrB(RbakphuROr+XEw zo2!mRH(Q>~V|6a`-O723`|H_iLJ6p?2%{uNe0bwptgTLyP#>lvh{=6Lm%{n z=jzg{XFl-wXsqAE20ilgAY0(+?CIALnhu$#g#HS7AITp-VZqbB UlNvkk9?Q@$ S#~kC!1}-Is>Wf>3 z;Qev;v&0}R=yFm?*L@3H6u6ye5>j>dwo(=06Yy^lbIe+wLH-kOcXL`1@sDpZEEP=6 z;Gb(4O6m@4U9a>rGeyb}J0&lHKTu%7r;(YIVrTs-T8ffrTd<}OM5nz@tE-Krwv4&A zYrzKr(!#@29$&Yq8+D&H6mitOl1eA=6bw>lXq>*{DLdmeTEACc;*Z=uw2RRWnpP?A zMtcn6z_3EGgeTg$9c!ntWzL!Q<~hv>@b&PV!#_v!l3f*=+X(Mp{W_QTdAGX4_wD(q zA}w333{L8gV7ZOSmhBI<4S#T0L{un;>H$)+7?E6M;xj4K73mY6zo#AJW8#XXw_i!8 zFgVKbL>ycZ&l7UJ&@7D28rXEuHS*gNW--D;aO mP|RM9qCmhuU=#e%ba2Qdef5 z`CVVzS|M+MEDz+D z5py1$y+UEY*0X{nPb-h2MS1@9ho?Va#rmL>E8cZL}T zS~jkKPaQod>07%Gt9{B?Y7q6?M-#YLi<5HB>8$RNBU!9+J_ywThkwR(FkowP#Y#-8 zU~536(5yQL^4nd3{-me15mw?nUg%C<>wX+|^?{Q%;UmsqD=0 j4bmmu2uPi=_TK-!_w&5x{qlZ!&vmZHPt(Qv%{Av-W6e3n z9QS=s6Zi)30uzN*$Q2PRw3)uo2D@()5|5oTuL;0 3RzE*?H0$D@QlOUA}W6Y)X; zjW+of2@VuYhGRdm{eOP!t^04@8aTWtc`~GaSv7$JRB1Xd_I3!4v%NKP%cbPf)kJ9A zg9V0H0ZZa&>&(FKY|{|LV3~l=s2EorIm6s>?%PtNBo?x^)38M@U_Iyh8?luiu)lPI z=e^j`o+oi;VC1=X`*1cwqSxcn-0|-FyM^4y+<{Gap=}xP1ypx(wdEM-Z?u&M&2$J( z#uV0^>V0`K$xScn90Lws8$jzFoJ{_u>JYP)WH;O1BZZaW2+B+s X%t;^i+1Z~aG-Y }< zjeez)GrY6MhdbYEJdYZF_2=K?w|v%0Ppr?Y?nIj}=*Z}~+XLHyXs`i@*5-kYpMeH% zj*=H6ND@OBVUbvG{U;tp!oA*wBzHd5C 8b}S z;IQsBqx+cSo@c?+wGs+hI7DP|9WtWO=Z9|Ug7+Df5O|i z#d1xu?B-&W!W@DJbzb}N9fWrH#a{gt5U=*_Kj?fHGIYpL$kigY<=>txVO%qGeG&q( za7QnN?{CQURCx7&CK*r8+vYrzRK8Ufhg&4+DH}#3kWnNb=r#F8P5ku0G&V0+4W!OG z7PE9eK5!WSJfsXe1N2@NfLDmFO10F=Nte88TvK(9@g`SarQqqNpVP*~Nw9rfkov%G z7QkhU-Y;I8$mtsXZ2i_Fx74*3c#+^Z)|%}G#N^@Zt*CqlsSDU9QIauaZCw^R?e8x| zvd%oU$Hk_lDFQEpQtl<}5FtFA6uqP!V&u!sZ )q@wcQ5LgabW41Tl@8J8TXkN?B5RXDdxo17(W0A@f#)yiiyz3 z+c0bSV$W26I6c2S5KNfHok9Wrh+p}J*=+PZ@4KzlPYV6#`*T5D=-1-(F Bh3!e?+Fvo&e+HS_5UOlWe|Jdbt}pw#!3Csot^bupszl$IXzuZxY-IgxM( z6cmK>>&|<;1#L4r5m(KLo*6s5x~n(jR#@nH7DPZ?uI`xD^s@36sZg|I13Chy*KBkg ztw-kGo4^q6>)Vh+zNygza|z&IaDjJ+5)#WPWq?tmbW>bpsil-PHjVMD*cnG<$?< z^S;CUt0RDY+UxX8RT1B+_euPHKG};CbW@&sO(;8V7HS7M)%lTp8(OA}5B#ZMm62Mf zrJ(e~eWODWb9pQ`dRB6OdHlx<^%C20JxztpNRd8Lfhd|Z+FSkiPo)yALkGqyEZ8vB zxtnmY!jlghzADs`@~}EptveOjsXr?#$(#i>F1#(f2$8wFIWpq1!k+J|R2w~*n*uE< z(K|(02#QAHcxobrX-2LS?IeOAUNNYmsa{d$Pj>HoQB`Km&=BSrO28)@R$(EF+dQ5n z#r5m 1+;^vLRr_E zZV0V_(o2U!7?;<+VQk73`K(hkX^iI$ Ttg zy3u#Le@8lgR?ny`M)a2gT^CcM=rdkv!E^}Tokr~&k6^5PAZeIX;|l}VBo17{gE-uT zSAFt3OZF6ewzAHGvV^1pK7RCT+d}=sDS=ami03tLFBna1+WRK*t%T?34fFj`4Rw?{ zTE%UbzG;qhBQy*&> A>FtAZrdY{)aBS z#Pk;!lIJ`D@*OMg< RoNi?B=dmD~6IBs!qO_X&s%l*O{6Eaw~Zy{DSL+Bzh>~?PkbXE(^WLDa~q^efhGC z3aj_=l%2-yp5j}qMDJ*=_%+k-$0Z*w_`B8#F lHNw&>3%ABivWfx} 54O(s@ z nY^VTHlLxp*0amiuQB$4jLN0MANm^OT6e8q zNY3h&$g b!D0j&2_crMFa=YJWe#@|}qAf 5aLQ)Wgx={TRZsmBuo@{0y5v@q)imY0-6zBO-HMig7>Lvu&QoI&Z zgXk2fb4A $lzqo0M(j@J{ZDOeT<%EbiN%*Dw2tzHczq8A?$0Q!1TU z@l!(rGoES%^>A8ub>xJ5kLFtGon$GeDcA;ZUY54YzdAfsW+404js)9E-{hy=%(UvY zhCt1+54nbA`6N^|uLp8S!UvkG>BVlV#!=pUiTg6hqSg_SmWqY%PDKB$BkLav@X0G7 zx~&)V))}c&6PP94v~Q!7{S+~6pUT@-9R*sV+@q5UZW+_x6{=mJ%%1=L_IqE&N)pPk#Mp5iO#xov=&d9P7tPRdZmK{Q+cE`9uw zO?Y&(i&o^hdh*e#PQmOZ%oaZk$KXqNCDPw4;*$bD+l=r%d%8Xrnr0EYQ?XKTRI8sT zkZGAWAzgit=#mhfUU!ZKPeNgf9}AP)+d3PAjB4v~pSgX6jL??P{;YPzf}Zhnog4YM z8Ly{ru2Ow|(` 4$CL|y$-V@%JB*iO+5Y9KOFFZanS%<7%9%D(>$5Lg< z#;`LC>lFFe3vm97LWQ50WL!(kav`3=dEipNGwwD$@;dI >)ffkl6AO%Vys}@^J zKPdr6ffz`tC3qY{vS$2BOv^Zl`!3t%o8*7QI!m_qJ0q}8BhdQZl#wekb-<4^%6)~! z$Q8-Jc&|LbBlg2`Kxj EEAZlNVl6;B8pJXz2 z7ug~kp-~POc`Mof6=UN49e5&x0kl0NB)7CRQIEjVM);{OFFtPsUKNt9YxEfr!)HT8 zri-tUPVQ?Y2x-CY4Lg9#okhZ(^o!q?I lGAP;s zyT)v9d7wrFq~Y>1A*$m83egB?`4w(tEjKP*W&q;BWl)NV0e3W$b34=?5_45Age1v> zA!Q#_W@5(97FmG>m_oa+BX5npV&}0Yy?)Xo0=u`Uq-nyqbWj;R{WE;8oybXmUh;~% zFZ(-0fk@D4KOG&N2x&w%!(VC`xiT=KPi^X>A|=B~+X`HDH-E42Jzegnqox(z(?H_B zM=vNzo^8)XfkXNgNEauxy^Vsf+jbfA_kKjBYa6KM0l%)R5${+`dTT0zD)} B2 zmET@rJzE{Wnryk;3N#ah)AOz01vHwma+&A9zornnT}J);RyFnSd11#n*hlJo0m`g<^fUd-7qus*n2U^yKJan7n 9t{!kPkfpy=vu>kU~>Yj3Jm#4~A zo~1o%7zck_h`KOW6Q~HOq12*-hj_mp_vb|Q*B(3wQUZNt{_%zrXrH`c529~~D8wPW zoq!O^sL9to5UW8tyCb^V%Jc>+!}?H6C1@@h(yWN8yym SLrgUHk)k;o5^Mj<&X1p`?Y!n$!0d0EzU-Khfz*QPR!q^ zK?D+a+W9l>rW+8N+2M9xVZz}FM61-3E4}5P44iu897+e vdv=q=rEg@jLfP U&nIABmY~5+KKgl zuwocJOLjHc2{)vyhk? u&0;?-Q;vzkrnNWl+E?t*9 z8Hv$IAO(qF&O1Lp;yW8l2m5&kerr=cAdM9f{>i2(lyS=>;rGMky25*( 16_U4#Mb(6(WB#h zwEMUnAn9?tdDxNPyrs$OIYINu=uncFyBX|;Z0~4D2uoA#v3S`mHjBTbZ2}^T!3>^a ze$U>@kmv?RsV|B6B3_^zXWQAqEQ#=zKgAruNurP93PSza!17|OI}kge7gd*|wTdn> zsy$j4?W)Z)I(w1%7rN;m?2r~dV9a6=t<1bz#|ZDOa(x{~;e7Z(2EaPJP>*&k*!g T|Ik)gBHK zZQ0Am*OQ~t<-aO$s;biNH&Jv}>5n HW@Um1(zsp5JzeB8 z8bGb@+f5ymi9v9Nq>xy5x zc@fwa$TR{LPLRB`JqwBwbc97!8F#PWH{?SkuH2 z$U^Ag9vU-sF7?)R*;pn%GaB{8=em9u{W$o$9wUOOA7;RPXQ9b`2Vt~&Nu#XtaWSLl zqfiO~UtZ_z?ZdU!JkPqUO!-gJ)(nAplC7gFqo4CW4XXJG(Y*ISGWXAPumx#Y>`9qP z88LP2Bu!Ee4-LaCVj@ZY18@5qJO*+vBj-tajEbb`g@w2*6f-Y==-eLtY(39Bj_;iP zqwGjnsTrM)V^umxq#cCp1f9#rJ8KWU!1uxOo?2Ab{>do1=f~*YemESAyNYA_c6gi= zf3YAuZ{*p{E9%h#4n l6aizd~(3f?vHDq|fP7Waonc|k@m z^xbn;-Uru5M;*LRLkVPut6#qqaq%Nk7uk|Ml*1;`ei`a2X+_4uzW&QUOY=3?z21*X z8E?)XU9Y&jS p2B4M6my)i7E+etmU`Pc0-@hSrKry_=)yap~lG zzG&m@6}58dn1&mLwRv7PvRhL@7m_cbyCbWYwP<((DBZ$>80 )gxWI)$E_ zXQ@0qEtgn-ciH9vd;c0oys1f4`1QWm(&U!XW#7@fBTd60Z>yi#8cj@dqwO#XSVAl~ z*;WLEy+)BqH 1h9 ?_*E@!V1cFjbNG=FHhkYq&^1Jih3AknzRLoGQL=l zBz<5wg;mJoWXN>Ft|!xP5_e%Oy)!eSxa~c)IXLf%#*Tuk!=^;zIhOab4(~r{!mN4X zym|6#wpAST&xcrJeoVL*?uc5rp7U5_W;X;5%yEJIr?4Z(m$(uo2VD5Wpr250^uvCR zZR&-jbF<$hWPn7+ByWX9ni|OwrfOXbq1U8zV|k5F{K D`Q-}h!t+8QFanHUK#h@C z%8jsvY}C#s$M#G#U>ZgSGbu<&+6mKAStyW_=sDUINeE*4Rr@iU7cg;&+a0RSo{nng zxNd%Dm`R DI^O+rL=I2MKg=IY`H`>`ALyk&Qlw~q8T5AYw#Hc-omcQoJ;^n3 z6^vg C;HSzBu8$ z#&@2Fb>HK8Ru&AwbgwO^HS?}X2c|+rtHo0cP@W&ROTh6@o72@pH8uXhrL>~y_|zrF z?)bUx$GFJ2OR_ *rmKSV?kZ>I-h_v2B@XJ4%OCcZ@~$`aM{v zsJP5@nW=ZZFYe+leL-v+=F2jzqQu)ftPID+5p1A63LU-AN YX;o7eN(#asP<^HnHjx!REwMO-MH(bdUVNx6U~Kw{fEi3-_G<8+fIV>z!#^h}vB zKk65yUcZg{9RhZSh5 bs_p5t03O=vxB6BvRwjdhLEz4EcI2oCG&n1|J1{uNJw zYK~W~b`sWfW*M)TGr6>7Ixq>MwXV*nQ;Q%6c3_C &C zKjc$b?dTobug8X?|Kem|IVt#E{w!gpzE9%of7|RqJQ*uI+upGgI8MWi?!2YEIS_4# z8;JRvFxN2`3o3IKa&E6GlJrJL*cn)@fZ(afixD@o70}#YSXGV3$)*oTRg`m}+Kh`K z_8`e-ET?CrBh*iKd-}79I%96CtqTB&!j%(=C1`^uHi%5 U67%nmuH|!Lp}JO;0HgNt&CjAxVXe!(P>mw0Ak}6Fs!e zJ$76?3DctbpTFshO~`0iiIl1*p4;-tAtCr?tUZ0fZ79e-QcEZT w(654nhWe&RJR9%!Zhq^a-dNj)P(Z7L#|(DW9Ff7K0Wp1$~`kmOR?Ybj~0j zr_hw_3}nmuEdw0GREF+YJ7JXX+Wo$3{gFpK(>I+URa=4n9^D#E9RCw_s?qlf!7Lab z^&_fLx4XJ)Gw#bM6fw!+P}R(5xN>B-S~pNP2_+J??@9}WmX?WZZ*blc@)mkdNi4ds zA%}8(NV}edOV|3+^q!XK^CL>qdpTJK_Ga9&R0K~t!oG2A2kIWnrpp$tsLgTg*60S@ zMOV8k(^I8H*4Smg%H`h@#Mxr!(9bNEb>S>b+EwVAP{0)$kkso5!tkVH*GJz+S@ff4 zKM-?rHGs*?_!qFpCcUagTj>-+;}S@mPR>%iO^M>&8Aub|3?t-P$m+`)>&HM%vPhL) zgN?z<>u&lfpf@1m{R4sh4~U)gHB8nr{ZxyXsjZ)9ZZ7hd2z_}c;$|mqZ$;)T^U-uw z{0k)+&T5eiZwefp!2B|8JX?^t8Nae{;D(i*lP|`0dX}}Mfl2H6T*ZTP>Xf#Va`aeD zn9kWBt-U@cMSE8f(7^Eow8>g`N!_e(Xy@8hI$~F+V@Tsxu9qpG?JJu--9RgAV%_%f znzER6xVcJa>S5Q?aYZM1Sh8D^bXw7X2aY*#yHC3mXqka-n7PLkygGNM)jMx=gY~?Z z(88eSF}Ia=oUwPcqpyMeHMWUd7Bso4GA@!mvh{S=`wEmkLk%6XZfTUGjBYDC*&=r- z=iQYe>bI&$xjx^x_CE4gsoJf=O=1Ha3;!U-t*N)3Y+F;MjD^3X*oX!1$>_)!)aK0e z${O1Bb9%Wj$<|$%p 1FhI*bx~vyB7W$>j6YSe z|5oGrXH7Lh6uRQ`n?JsiT!rWSiADKG?tc(=Yl CuAP`X>g>=Z 37rR?SEUKk*e2JZ>X5M3)My_$ zqz!x*f|M~v5M>!6*JDonVK}&_9_Olk=)X3_eW;+CKl#nD3+30`GTW_G$AYwhIz;Bu zf8jyFIQYF5lK&SGx(=aG@WdRP!`DUB%^u9!FS41CA#Dh4PJJbL6FOyv$xy3|aTCU- zs9u?l6@M5uR_eWI9dOwGISq~rVQ+ri5^^Fh`deCzi55Cgj2>Zp37r-(E0as%QUCLr z|NnZ;zGp)d)phy;3*K~RPt`NO? f;LFM%^^eIhi0wsN0Ko`hC-#(m&E;i8llplc!ecAAvUNq=U&W9Ng53Qpc|V9 zoEY4Aqznd& HHPEaR20jd|S7b~mw)7@*QafF!)NY4^9z6)!Kr9fVO{OU0vl)rQ zU@TXM4Q(JaOq0hNqk_Ip%E oHQYqQGj+vow z6xf?)^z?^HrZ^VqzZ&5yVEp9m&r=t_)$z)q0EgTj3|}CWC`n2A>f~rjZEtUL ^a94$$mxzAbm5jGhc5EAI04Bl43acy+xyvp?{de0*G>9~*k#e31J?;X0rE*Kp*1 z2^ci5-6$<4xPJs*l$p$HPZlWuJvvan2k+F3`rSAylndau1}hi$kb8Qdoc3u!5viX7 z>SrI7je@eII@BqA|7Qwvy&A5&yURR8t`|Vt%Gc3a70}SJxG0!Vx`U+Oq>wT>l>5&q zBV`+C!*DmY6*LWN6}mq>LiJu6$_|$$UE8R|aOI(#4=+=9l7&DP%4z4BP_io>JxE{P z!gINwhUk$&AMv0}#^i_x15~F4R*)W+ZUbc|eR%ygUyF^A>wlX)v`rq`Mm=w4j 5DL2pZA}&M#|^lS=TN2WRUh~gI#8%HJc-C zaE0-i8F(Tef`)+?b4AjXBRZG_8Rw7C=B6)u=jhO}NICBLY}Up4!W~SSacI-Nj+k&! zG9?&_6c|Vm{wxf?!DEVyfluYiWpRJ_Pm{KfD9J|~Q9oojlwmmRoRNz0?0)(>!WHP} z74!xfZB-9E_)l;qnhV2&5oesR|8mDuxMTT|2lBj4J*dv8^ld$2HI~ro-kI6&$Vhm@ zNSwKS3r0pl6|VDL`s#&@v Bg)3$V2ZTR)hZ95U~hu_@)fj89pPHG6KCc8+u!f zlsn zu<4#*T^VxL!ITCKi?tOo8fv$yo@DsJsd__*cL4a zu^Jn&n&CmRWyDfeVWfxq2sx2*KD0r%2j?YXHThsQx+51}{$VxjU^R(i3dk_oz%UI@ zFfYqMnFTCb_FU&Q(wD!d4d^2Wv_Xr*@f`BD)!=Q%d!}0 ecu)iVhLLv`D9+Zj2zAb06oIz*tBq`k7O0R!>x6Rbi@V;uy5K7BRQxE zjSQgOX~VvK2J)V 7(8Zdt%5B8CK;lfBj zK#-xHC^GhR>iCb}a)8y^@%`};QxYtW2LV>;1rF}GUWo;B#e(l&6@UfUG5mQCnQpnm z!y-GqY)(iTlhglEKUUe!A9KYB)o{_%Y*h6D;fOiI-q(e2AMrQ=51l;mg4Z4NzNo&! z^);Hd`vN2m*%q8~Xz1DSyD6PYjtl6eScA5s%zYO=ye)mC_UgIk(HLiv6`7rC%JXM2 z>-azSqLS!o6w^g+)8!>OkV)2^hcDr3VwyS@9VK34V~MZ%*9oyodOGw=tW)8VUd_r5 zOJY3a3}cqmuUV!tV2%CyMe7EJEqoedixjtiGfTQ|rbmQgp#Olu{o)e*`|qWETo@{L zyq0c)l>nn6G;;H$d>s*7qV-Hu{(HSLse~GT5o &p1$keeHK_52sH`WR5Es-~2E&m6HI~28;>A!WfVs_vR*rQF9Hja%aji^7xLM+N z8wY8Tq0gp?dr%I^-+ovkOkhGod<61!O7QsT$LSVWWAKo&9`^UU;I&LCFj8Y ! %4;?>j?Bb^@cMyX|j@Z~f#bu=Q zDulD&dJByu;M~&y ;VD8exXj75ufS5|}*mVe%}N*EefT6m@wcewniU-F2Cyc!9hozc1Ha z7@cVPa33xAf>IyHr4P@#EmadiQIUIX 91;XB*gW^=%un2S>Q21%QTr*J4RKoTB;YGmdGO>Ni3#|<)66buvY8)DSY zj@AMuKbQ@wt0v#PrmN92G?vJ51=U|@Y%Cu^a|sgD(bIsH2ULdV^gh4|xGG2zxnHXm zy^!yyO^4NM I^ZZE2vA&Y7LKi~h0*~`LN 2ZIYYG;ha~h2S6 M+?c zFz{qy&Ip!{HDw~b1ut`*wy-|6@ddlG#wR?z4aIYe;wU^ETmllmCso&71!eiB?^l*r zl1(hR9!v+#1317zV)5M_sK9cT`X&Y$e3C3UKke|ElwcyzDBb(2 -++9=l*OF&XV zES-I0$^EibJUc%Twa5)!m--9U&`&qV@L)+gm=o<+MNH+HSfCNVQm?*yiUT{kuNs#J zqTzrwoCx`AmMh3fg%^1xjXZ#rIM<`V@BDK*^=tIQ^|A6^IACnbkQ`BJ9OHG}^xBAP ztHDx$)q@yTA4+g$IAVlxFs{RTBHMS6Diau7(|SL=JUE%};Wsah8}E&(E5@vh7+jjj z6xtG9#xGUW3Ey&IXxL0)T^sxmZ{X1*S6}f`i9}pi$TjH;Wydq W36tQF^awdpV$E>FgN~@{4#?WlV4y uo zrfUd3iL(V$-JD~s{!uhkwfGng g6-lwck!FuM}2Xf{0 zD9@$5Fj{;PvP2yJpx_kYa~Rjb)S8a5Ob+nOZo! i62b;dbFBYv5UR9YMUE`s zR^p4|$}u^^SPp+quw)p)MMEQ>R+5#{L1!gRgw}~zhx;S`JU9kvFvH8HwqGX=&V@f@ zg_#cv!>2OmpXkRzDhflks@Lg57Od{GNt!62ApMuauLbL5n&!eqye1v^LthL3Y64tw zXy}w83r6a9zGhyBCksnnaAzDVnCuiCOn-m-uMar?v_a>;W3eWr{>nG}CRk}OQ^=Zk zL_w?mpZ3sHvUN_B9eR~e Y{5m4sRukZ~Gih{_IL-JnXg+ zN_Oh@4{FozLMMaJWSaTSWAko2S=LA^S0O<8%oQhz&A$Mp*TMXInHPi~wO_QH$dBEv zS>_ypGi$&39j-m`z6uWAE99(7Ky2JP^}1XGUUKb&txdbtkF770M0u( ;u6(yT}Hxo4cgV^F)6~L)yg LU*U7vXwhr;ox>r7w&21?E*(hEYc!y+D%m0b1>0Ku+s=g5QSzXU_zXkW_>tb zF+O{x!d;^qck?k#u-ESq^KC$Qd v6f`_~a5JOHcYRpKM7Z@}{8>=NHk z@Yt-!m8 ~o0CD*-2D94dyi_fotD}5NJP2HgeHsb`&AX2_*TIQ q zqG4KP0+zk96|uQCddXfcLj{K0J@0RCm)4%B-HAQxzm}l=wyclpbc-Zb EV1DRhBm&)d0wD8fw}oQMuQijc?2$ z8p|5uhq4oVRMo!|0r-0xIsFwhLarsup0-MyYE>B9Ns-E5co*_}98ONjGd`BS;fm La?T4~<#KU!B2L0P2qVQ)2;f{TM| z{qRvd;r7yH&E+g~yY|H%siJtGw~*=*-~6dL#BKPT@St9B;$r^?--UxOkNrq9_Bz0G zL*zdW0rsnmO4j~Z-NltuXx7kshobXMd`<2CguC{9ieE<1(!J=Guz6=k;!B_2_-)or zwm^^<0B?=pVYsiPDs~z%$v2eEf!*O^dIUi;kI V>3pu9o+LEJE5_HtcuU)s+>|T@K ziT$$h+_9-G3iQ;s8{(XNKTOac)*A!|Y3TQME&WXPSk9eFld!M6-<`swW5KIb<)?jI zvTV}OxOW+~$n%@#4LG%02T@TAch}M*#4GHgp1n8iAv7Rj &rJoI(5$#hVw#3^dI_To*YtD_5?^>9s+b&3wy0){Eg1$%JqoQLeKsbxPf0y! zxIpYXL0wjLx3EF<{KN5Q97|`m3bA$0KR%S=>{&A5VJ6|c>0Gc>a|^PxY+$h<5|#DS^M+A6&s><`ZV@r5564MF|Yf-GhJAB@wI=m k_>61-47{%T^i)$6O{~Yd!j{F_T4g__~9V zrJ6&1%*pMIx}|cJ?%-yoOYrWHb2UW Z6L=Wv5I7o4mtgJ(lL{t0;^1v%@a$ z_15f-7v0`Y^g-&kmB&iDMxTtoA|jdpIAEzY?!L?6unJ4tcc)L{@*8?f<9Yut_LD)w z(0P-p^lf~|*i4_`%MFkG!%Jncv6T!)9)VOvFKWqyCY3IucKLVKL}Qn(iacDJJUeyC z9X}`G5>oz2(}V1@mlDrzd)J^8Q+J3|b@xzned)OK53i&CT+y?=zT^vmxYbMr)(fL6 zg=3jw^@>znD|+F*UwBb5PUab|u8I%2+)zX$xM$|?wPv jHhRcf&&q!Zh^_^;2)9I`x#Qk8nP5?fy*lo39*JV#nGmT6)pkkIotyPf4+& zsX8b#!ybtRO 1zbaaWo!V75VK`+Lmyz-rt zkbuI9-^+X(s6`f2LpIupXWbz`ljE}~U+*`&=&5o}Gr#_5CW`V88KB616Hq2%xH43G z`n0^kZ{lltAD8o_m0Q}a_b(;zRj4wor}~ VHWzEqjRd?A}r%}cahsdlU`HZeTcSr#3(h0`oFlyT=q1<&Spi#uzu+*th zrfeZKLr_1tyCAV&_+V;E36&k2&9gM>TPuFzw0W_sX^nTs;&yd>ipDo2I<2pI=E?43 z6lwG*=^&;oVDB>byP8g}o8n(lStS`J+L_9)>!gP?=qyw&p%#A!nYF%J+w5*NC|jur zoCz5p&`jpdbg|d2-bv9gr~rhg>`|J_OLD57puma(sqBaVF_jXr0o1QAh4-9{{(KdF zYTof$gcx}K@G?5Fk*yBN_tR2@J?t|exWMW9AgU&r+1{% x4= zIpB^^y<0AMYZ@VTQ+M@!h*6^&WBmKtFYJLqvmLBTLGK{SlApP}Q{6PR%kF6JiO^_M zXl?P5PK-z2D7z{? a1wu`XV)zy z24%NXg!k{#?5w}E>#Lb`*y*TGOliX24C^qx)f(ISy^~) IgYQ@vplS zqVj0}BnnA|iX@*k_!*Tg&0g8-dHq`)-Sb !$7 z0g{SjyXE$u3n$nB#6M?rn#E(?w~tyB@!u3a`Ym+FlPj+?YRKiHMYm8(6`nfdVXoue z6aLRZRHu4{fy|y<76(Q?Zx#}9-S%aF?pRxtH~EL#5e{K^KzzOZ{#mK->2^WHxsw6= z)~|VmHJtmgqO`c9+W7ELot9S}n<+fqeU(@u^C}yQt9(WkcQU`=TNl2Qn{f-0og_?6 zRb|Z9yW`qjmsPJexpKYH-*3jHXg8{eu%q&Z6Jf{R{PT(pnT|@SUG;v-9dc1a@AP^( zV~f)EF|A%B39Hk}g4XWO)HIxX?w6_s*(kpF {wXcQx;LEz;z&1TQ&7`VK?PLn z=COieD>13Rftmeqry>9G!dB+hjw9M#OW|g!tJ&^9ndb(nLY8Qg_BsP6Zg=mCqfEIU z?Jdc^h&ReuiO<=4Yw0=iLw={YNbX4$^QV{9`1fklT_`&RdEQu?B+Y&5L&e=rPT(Iv zWj*yNkM3!&>M2ubpm2U!dtYy(VzF?BupRq-jebhLplUV^LrD#M6)-SQEeiLVtwcH$ z8)s%NsSse!@b0*dWIgh&!V&W1ce*#t?4e_2c_!GaFulxTHNJbj;P*_asp?dYRaVdk zfoXd<8>N1f%FsI1JBxW;ox;}X`bqiEBwDVS!N)IhwI*vdM`^SAK3_t)Zc{NgO4jki zrjR+&Lm|9jHqD7mlfH{N*ywk$Ty^sP*e@P~^h?>e#CwH0E4Xi9Emf!sc3hJLVy2l) zAVjY3ZPC1dAr9fL2TWjg5H?LIXXj9$m%>2X?nf+>73o1F_0*lC^Y6##LcCj4ay=#h z&d&44WvpnsXaFI0S?`r)*}BK@j2|cIZdm#7Slw}kmpiIZ<2@N_VvIMicgywo+(YIm zKX_~qHy_SjXh?z6OA#_VeYnZ_EQbL-4<37`G-w6|akJ~7W}u%>COkuhR^}ew0W8~_ zsUe~De{Y5VPuKo0erx{I)0JV#A(WH{ylgE l^Vpv63hItT9Z;twT_mvDjEK)l# z&MBuO4hHl}?3(e93U9o(636AV>0zlXvDKA1%~O`^l5e(yOyUJF%xj6DUiD}C2+0F+ p!9JAk1k*PpN%_~+|ATHj@b-(xig(X!mO_JnN^ Date: Sat, 27 May 2023 08:43:39 +0100 Subject: [PATCH 07/36] Removed repeating subtitle --- go/ql/src/experimental/CWE-176/UnicodeBypassValidation.qhelp | 1 - 1 file changed, 1 deletion(-) diff --git a/go/ql/src/experimental/CWE-176/UnicodeBypassValidation.qhelp b/go/ql/src/experimental/CWE-176/UnicodeBypassValidation.qhelp index 7f19defaa798..9d3ad0f02e0d 100644 --- a/go/ql/src/experimental/CWE-176/UnicodeBypassValidation.qhelp +++ b/go/ql/src/experimental/CWE-176/UnicodeBypassValidation.qhelp @@ -1,7 +1,6 @@ - Security checks bypass due to a Unicode transformation
If ever a unicode tranformation is performed after some security checks or logical validation, the From 84e071c1502f1e2267d9353b64863aeb5963d7f4 Mon Sep 17 00:00:00 2001 From: Sim4n6
Date: Sat, 27 May 2023 08:44:43 +0100 Subject: [PATCH 08/36] No redundant imports --- .../semmle/go/security/UnicodeBypassValidationCustomizations.qll | 1 - 1 file changed, 1 deletion(-) diff --git a/go/ql/lib/semmle/go/security/UnicodeBypassValidationCustomizations.qll b/go/ql/lib/semmle/go/security/UnicodeBypassValidationCustomizations.qll index 7a0e9d7b1fcc..ae721c1ab469 100644 --- a/go/ql/lib/semmle/go/security/UnicodeBypassValidationCustomizations.qll +++ b/go/ql/lib/semmle/go/security/UnicodeBypassValidationCustomizations.qll @@ -5,7 +5,6 @@ */ private import go -private import semmle.go.dataflow.DataFlow /** * Provides default sources, sinks and sanitizers for detecting From d5d67760977cd4825872c463e9e3126a1d2305ff Mon Sep 17 00:00:00 2001 From: Sim4n6 Date: Sat, 27 May 2023 09:02:38 +0100 Subject: [PATCH 09/36] all forms are vulnerable --- go/ql/lib/semmle/go/security/UnicodeBypassValidationQuery.qll | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/go/ql/lib/semmle/go/security/UnicodeBypassValidationQuery.qll b/go/ql/lib/semmle/go/security/UnicodeBypassValidationQuery.qll index 9a164a4fe454..3dfe35a500a2 100644 --- a/go/ql/lib/semmle/go/security/UnicodeBypassValidationQuery.qll +++ b/go/ql/lib/semmle/go/security/UnicodeBypassValidationQuery.qll @@ -60,11 +60,9 @@ class Configuration extends TaintTracking::Configuration { /* A Unicode Tranformation (Unicode tranformation) is considered a sink when the algorithm used is either NFC or NFKC. */ override predicate isSink(DataFlow::Node sink, DataFlow::FlowState state) { - exists(string unicodeNorm, Constant vulnerableForm, DataFlow::MethodCallNode cn | + exists(string unicodeNorm, DataFlow::MethodCallNode cn | unicodeNorm = package("golang.org/x/text", "unicode/norm") and cn.getTarget().hasQualifiedName(unicodeNorm, "Form", "String") and - vulnerableForm = any(Constant c | c.hasQualifiedName(unicodeNorm, ["NFKC", "NFC"])) and - cn.getReceiver() = vulnerableForm.getARead() and sink = cn.getArgument(0) ) and state instanceof PostValidation From 4ce9aa99a91fe43e84a5a1f8e946af40a6526d7b Mon Sep 17 00:00:00 2001 From: Sim4n6 Date: Sat, 27 May 2023 09:03:08 +0100 Subject: [PATCH 10/36] Copied a go.mod and more --- go/ql/test/experimental/CWE-176/go.mod | 5 +++ .../CWE-176/vendor/golang.org/x/text/LICENSE | 27 ++++++++++++++ .../CWE-176/vendor/golang.org/x/text/PATENTS | 22 +++++++++++ .../x/text/unicode/norm/normalize.go | 37 +++++++++++++++++++ .../experimental/CWE-176/vendor/modules.txt | 3 ++ 5 files changed, 94 insertions(+) create mode 100644 go/ql/test/experimental/CWE-176/go.mod create mode 100644 go/ql/test/experimental/CWE-176/vendor/golang.org/x/text/LICENSE create mode 100644 go/ql/test/experimental/CWE-176/vendor/golang.org/x/text/PATENTS create mode 100644 go/ql/test/experimental/CWE-176/vendor/golang.org/x/text/unicode/norm/normalize.go create mode 100644 go/ql/test/experimental/CWE-176/vendor/modules.txt diff --git a/go/ql/test/experimental/CWE-176/go.mod b/go/ql/test/experimental/CWE-176/go.mod new file mode 100644 index 000000000000..d3ce8ae30bc1 --- /dev/null +++ b/go/ql/test/experimental/CWE-176/go.mod @@ -0,0 +1,5 @@ +module main + +go 1.20 + +require golang.org/x/text v0.9.0 diff --git a/go/ql/test/experimental/CWE-176/vendor/golang.org/x/text/LICENSE b/go/ql/test/experimental/CWE-176/vendor/golang.org/x/text/LICENSE new file mode 100644 index 000000000000..6a66aea5eafe --- /dev/null +++ b/go/ql/test/experimental/CWE-176/vendor/golang.org/x/text/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/go/ql/test/experimental/CWE-176/vendor/golang.org/x/text/PATENTS b/go/ql/test/experimental/CWE-176/vendor/golang.org/x/text/PATENTS new file mode 100644 index 000000000000..733099041f84 --- /dev/null +++ b/go/ql/test/experimental/CWE-176/vendor/golang.org/x/text/PATENTS @@ -0,0 +1,22 @@ +Additional IP Rights Grant (Patents) + +"This implementation" means the copyrightable works distributed by +Google as part of the Go project. + +Google hereby grants to You a perpetual, worldwide, non-exclusive, +no-charge, royalty-free, irrevocable (except as stated in this section) +patent license to make, have made, use, offer to sell, sell, import, +transfer and otherwise run, modify and propagate the contents of this +implementation of Go, where such license applies only to those patent +claims, both currently owned or controlled by Google and acquired in +the future, licensable by Google that are necessarily infringed by this +implementation of Go. This grant does not include claims that would be +infringed only as a consequence of further modification of this +implementation. If you or your agent or exclusive licensee institute or +order or agree to the institution of patent litigation against any +entity (including a cross-claim or counterclaim in a lawsuit) alleging +that this implementation of Go or any code incorporated within this +implementation of Go constitutes direct or contributory patent +infringement, or inducement of patent infringement, then any patent +rights granted to you under this License for this implementation of Go +shall terminate as of the date such litigation is filed. diff --git a/go/ql/test/experimental/CWE-176/vendor/golang.org/x/text/unicode/norm/normalize.go b/go/ql/test/experimental/CWE-176/vendor/golang.org/x/text/unicode/norm/normalize.go new file mode 100644 index 000000000000..a6cfe72443a3 --- /dev/null +++ b/go/ql/test/experimental/CWE-176/vendor/golang.org/x/text/unicode/norm/normalize.go @@ -0,0 +1,37 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package norm contains types and functions for normalizing Unicode strings. +package norm // import "golang.org/x/text/unicode/norm" + +// A Form denotes a canonical representation of Unicode code points. +// The Unicode-defined normalization and equivalence forms are: +// +// NFC Unicode Normalization Form C +// NFD Unicode Normalization Form D +// NFKC Unicode Normalization Form KC +// NFKD Unicode Normalization Form KD +// +// For a Form f, this documentation uses the notation f(x) to mean +// the bytes or string x converted to the given form. +// A position n in x is called a boundary if conversion to the form can +// proceed independently on both sides: +// +// f(x) == append(f(x[0:n]), f(x[n:])...) +// +// References: https://unicode.org/reports/tr15/ and +// https://unicode.org/notes/tn5/. +type Form int + +const ( + NFC Form = iota + NFD + NFKC + NFKD +) + +// String returns f(s). +func (f Form) String(s string) string { + return "" +} diff --git a/go/ql/test/experimental/CWE-176/vendor/modules.txt b/go/ql/test/experimental/CWE-176/vendor/modules.txt new file mode 100644 index 000000000000..a9c7845cc17a --- /dev/null +++ b/go/ql/test/experimental/CWE-176/vendor/modules.txt @@ -0,0 +1,3 @@ +# golang.org/x/text v0.9.0 +## explicit; go 1.17 +golang.org/x/text/unicode/norm From 01117764e9e8d7d648c2725951427d885af3cee1 Mon Sep 17 00:00:00 2001 From: Sim4n6 Date: Sat, 27 May 2023 09:08:33 +0100 Subject: [PATCH 11/36] update some metadata --- go/ql/src/experimental/CWE-176/UnicodeBypassValidation.ql | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/go/ql/src/experimental/CWE-176/UnicodeBypassValidation.ql b/go/ql/src/experimental/CWE-176/UnicodeBypassValidation.ql index 850fa4f81cf8..ce591801575c 100644 --- a/go/ql/src/experimental/CWE-176/UnicodeBypassValidation.ql +++ b/go/ql/src/experimental/CWE-176/UnicodeBypassValidation.ql @@ -1,13 +1,15 @@ /** * @name Bypass Logical Validation Using Unicode Characters - * @description A Unicode transformation is using a remote user-controlled data. The transformation is a Unicode normalization using the algorithms "NFC" or "NFKC". In all cases, the security measures implemented or the logical validation performed to escape any injection characters, to validate using regex patterns or to perform string-based checks, before the Unicode transformation are **bypassable** by special Unicode characters. + * @description A Unicode transformation is using a remote user-controlled data. The transformation is a Unicode normalization . In all cases, the security measures implemented or the logical validation performed to escape any injection characters, to validate using regex patterns or to perform string-based checks, before the Unicode transformation are **bypassable** by special Unicode characters. * @kind path-problem - * @id py/unicode-bypass-validation + * @id go/unicode-bypass-validation * @precision high * @problem.severity error * @tags security * experimental * external/cwe/cwe-176 + * external/cwe/cwe-179 + * external/cwe/cwe-180 */ import go From df556e914d5e6db19e1e992c37bbe88cdf1a0560 Mon Sep 17 00:00:00 2001 From: Sim4n6 Date: Sat, 27 May 2023 11:58:29 +0100 Subject: [PATCH 12/36] add CWE-180 to metadata --- go/ql/src/experimental/CWE-176/UnicodeBypassValidation.ql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go/ql/src/experimental/CWE-176/UnicodeBypassValidation.ql b/go/ql/src/experimental/CWE-176/UnicodeBypassValidation.ql index ce591801575c..80964274c3b3 100644 --- a/go/ql/src/experimental/CWE-176/UnicodeBypassValidation.ql +++ b/go/ql/src/experimental/CWE-176/UnicodeBypassValidation.ql @@ -9,7 +9,7 @@ * experimental * external/cwe/cwe-176 * external/cwe/cwe-179 - * external/cwe/cwe-180 + * external/cwe/cwe-180 */ import go From 07bbfde7a0774cc86c9c372a5cdb0df763ddbec7 Mon Sep 17 00:00:00 2001 From: Sim4n6 Date: Sat, 27 May 2023 11:58:54 +0100 Subject: [PATCH 13/36] String manipulation incorporated --- .../security/UnicodeBypassValidationQuery.qll | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/go/ql/lib/semmle/go/security/UnicodeBypassValidationQuery.qll b/go/ql/lib/semmle/go/security/UnicodeBypassValidationQuery.qll index 3dfe35a500a2..fc22d2e8b607 100644 --- a/go/ql/lib/semmle/go/security/UnicodeBypassValidationQuery.qll +++ b/go/ql/lib/semmle/go/security/UnicodeBypassValidationQuery.qll @@ -51,8 +51,22 @@ class Configuration extends TaintTracking::Configuration { nodeFrom = cn.getAnArgument() and nodeTo = cn.getResult() ) - //or - //stringManipulation(nodeFrom, nodeTo) + or + exists(DataFlow::CallNode cn | + cn.getCalleeName() = + [ + "Clone", "Compare", "Contains", "ContainsAny", "ContainsRune", "Count", "Cut", + "CutPrefix", "CutSuffix", "EqualFold", "Fields", "FieldsFunc", "HasPrefix", "HasSuffix", + "Index", "IndexAny", "IndexByte", "IndexFunc", "IndexRune", "Join", "LastIndex", + "LastIndexAny", "LastIndexByte", "LastIndexFunc", "Map", "Repeat", "Replace", + "ReplaceAll", "Split", "SplitAfter", "SplitAfterN", "SplitN", "Title", "ToLower", + "ToLowerSpecial", "ToTitle", "ToTitleSpecial", "ToUpper", "ToUpperSpecial", + "ToValidUTF8", "Trim", "TrimFunc", "TrimLeft", "TrimLeftFunc", "TrimPrefix", + "TrimRight", "TrimRightFunc", "TrimSpace", "TrimSuffix" + ] and + nodeFrom = cn.getAnArgument() and + nodeTo = cn.getResult() + ) ) and stateFrom instanceof PreValidation and stateTo instanceof PostValidation From 3c90261f359147a0af338197cb1703805da0bdb3 Mon Sep 17 00:00:00 2001 From: Sim4n6 Date: Tue, 30 May 2023 11:16:12 +0100 Subject: [PATCH 14/36] Delete change note for UBV --- .../2013-05-02-post-unicode-normalization-query.md | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 go/ql/src/change-notes/2013-05-02-post-unicode-normalization-query.md diff --git a/go/ql/src/change-notes/2013-05-02-post-unicode-normalization-query.md b/go/ql/src/change-notes/2013-05-02-post-unicode-normalization-query.md deleted file mode 100644 index f9696176ebf9..000000000000 --- a/go/ql/src/change-notes/2013-05-02-post-unicode-normalization-query.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -category: newQuery ---- -* Added a new query, `go/post-unicode-normalization`, to detect a misuse of a post-unicode normalization. From f6394bb0f183c98f7c3de486e0f9212724973348 Mon Sep 17 00:00:00 2001 From: Sim4n6 Date: Tue, 30 May 2023 11:17:08 +0100 Subject: [PATCH 15/36] Update go/ql/src/experimental/CWE-176/UnicodeBypassValidation.qhelp Co-authored-by: Chris Smowton --- go/ql/src/experimental/CWE-176/UnicodeBypassValidation.qhelp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/go/ql/src/experimental/CWE-176/UnicodeBypassValidation.qhelp b/go/ql/src/experimental/CWE-176/UnicodeBypassValidation.qhelp index 9d3ad0f02e0d..3ff907ffb42b 100644 --- a/go/ql/src/experimental/CWE-176/UnicodeBypassValidation.qhelp +++ b/go/ql/src/experimental/CWE-176/UnicodeBypassValidation.qhelp @@ -3,8 +3,8 @@ If ever a unicode tranformation is performed after some security checks or logical - validation, the - latter could be bypassed due to a potential Unicode characters collision. + validation, those + checks could be bypassed due to a potential Unicode characters collision. The validation of concern are any character escaping, any regex validation or any string verification.
From 5ff385e94caf3a53e63d4700ecfe782537549d7d Mon Sep 17 00:00:00 2001 From: Sim4n6Date: Tue, 30 May 2023 11:27:12 +0100 Subject: [PATCH 16/36] hasQualifiedName use rather than getACallee --- .../security/UnicodeBypassValidationQuery.qll | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/go/ql/lib/semmle/go/security/UnicodeBypassValidationQuery.qll b/go/ql/lib/semmle/go/security/UnicodeBypassValidationQuery.qll index fc22d2e8b607..e29114ebd945 100644 --- a/go/ql/lib/semmle/go/security/UnicodeBypassValidationQuery.qll +++ b/go/ql/lib/semmle/go/security/UnicodeBypassValidationQuery.qll @@ -53,19 +53,20 @@ class Configuration extends TaintTracking::Configuration { ) or exists(DataFlow::CallNode cn | - cn.getCalleeName() = - [ - "Clone", "Compare", "Contains", "ContainsAny", "ContainsRune", "Count", "Cut", - "CutPrefix", "CutSuffix", "EqualFold", "Fields", "FieldsFunc", "HasPrefix", "HasSuffix", - "Index", "IndexAny", "IndexByte", "IndexFunc", "IndexRune", "Join", "LastIndex", - "LastIndexAny", "LastIndexByte", "LastIndexFunc", "Map", "Repeat", "Replace", - "ReplaceAll", "Split", "SplitAfter", "SplitAfterN", "SplitN", "Title", "ToLower", - "ToLowerSpecial", "ToTitle", "ToTitleSpecial", "ToUpper", "ToUpperSpecial", - "ToValidUTF8", "Trim", "TrimFunc", "TrimLeft", "TrimLeftFunc", "TrimPrefix", - "TrimRight", "TrimRightFunc", "TrimSpace", "TrimSuffix" - ] and + cn.getTarget() + .hasQualifiedName("strings", + [ + "Clone", "Compare", "Contains", "ContainsAny", "ContainsRune", "Count", "Cut", + "CutPrefix", "CutSuffix", "EqualFold", "Fields", "FieldsFunc", "HasPrefix", + "HasSuffix", "Index", "IndexAny", "IndexByte", "IndexFunc", "IndexRune", "Join", + "LastIndex", "LastIndexAny", "LastIndexByte", "LastIndexFunc", "Map", "Repeat", + "Replace", "ReplaceAll", "Split", "SplitAfter", "SplitAfterN", "SplitN", "Title", + "ToLower", "ToLowerSpecial", "ToTitle", "ToTitleSpecial", "ToUpper", + "ToUpperSpecial", "ToValidUTF8", "Trim", "TrimFunc", "TrimLeft", "TrimLeftFunc", + "TrimPrefix", "TrimRight", "TrimRightFunc", "TrimSpace", "TrimSuffix" + ]) and nodeFrom = cn.getAnArgument() and - nodeTo = cn.getResult() + nodeTo = cn ) ) and stateFrom instanceof PreValidation and From 8cc03c6f85626d9a22e25bccc9a405617feacab9 Mon Sep 17 00:00:00 2001 From: Sim4n6 Date: Tue, 30 May 2023 19:41:18 +0100 Subject: [PATCH 17/36] Use of getResult and more --- .../security/UnicodeBypassValidationQuery.qll | 44 +++++++++++++++---- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/go/ql/lib/semmle/go/security/UnicodeBypassValidationQuery.qll b/go/ql/lib/semmle/go/security/UnicodeBypassValidationQuery.qll index e29114ebd945..e59a6e92df36 100644 --- a/go/ql/lib/semmle/go/security/UnicodeBypassValidationQuery.qll +++ b/go/ql/lib/semmle/go/security/UnicodeBypassValidationQuery.qll @@ -56,17 +56,43 @@ class Configuration extends TaintTracking::Configuration { cn.getTarget() .hasQualifiedName("strings", [ - "Clone", "Compare", "Contains", "ContainsAny", "ContainsRune", "Count", "Cut", - "CutPrefix", "CutSuffix", "EqualFold", "Fields", "FieldsFunc", "HasPrefix", - "HasSuffix", "Index", "IndexAny", "IndexByte", "IndexFunc", "IndexRune", "Join", - "LastIndex", "LastIndexAny", "LastIndexByte", "LastIndexFunc", "Map", "Repeat", - "Replace", "ReplaceAll", "Split", "SplitAfter", "SplitAfterN", "SplitN", "Title", - "ToLower", "ToLowerSpecial", "ToTitle", "ToTitleSpecial", "ToUpper", - "ToUpperSpecial", "ToValidUTF8", "Trim", "TrimFunc", "TrimLeft", "TrimLeftFunc", - "TrimPrefix", "TrimRight", "TrimRightFunc", "TrimSpace", "TrimSuffix" + "Contains", "ContainsAny", "ContainsRune", "Count", "EqualFold", "HasPrefix", + "HasSuffix", "Index", "IndexAny", "IndexByte", "IndexFunc", "IndexRune", + "LastIndex", "LastIndexAny", "LastIndexByte", "LastIndexFunc", ]) and + nodeFrom = cn.getArgument(0) and + nodeTo = nodeFrom + ) + or + exists(DataFlow::CallNode cn | + cn.getTarget().hasQualifiedName("strings", "Compare") and nodeFrom = cn.getAnArgument() and - nodeTo = cn + nodeTo = nodeFrom + ) + or + exists(DataFlow::CallNode cn | + cn.getTarget().hasQualifiedName("strings", "Cut") and + nodeFrom = cn.getArgument(0) and + nodeTo = cn.getResult([0, 1]) + ) + or + exists(DataFlow::CallNode cn | + cn.getTarget().hasQualifiedName("strings", ["CutPrefix", "CutSuffix"]) and + nodeFrom = cn.getArgument(0) and + nodeTo = cn.getResult(0) + ) + or + exists(DataFlow::CallNode cn | + cn.getTarget() + .hasQualifiedName("strings", + [ + "Fields", "FieldsFunc", "Replace", "ReplaceAll", "Split", "SplitAfter", + "SplitAfterN", "SplitN", "ToLower", "ToLowerSpecial", "ToTitle", "ToTitleSpecial", + "ToUpper", "ToUpperSpecial", "Trim", "TrimFunc", "TrimLeft", "TrimLeftFunc", + "TrimPrefix", "TrimRight", "TrimRightFunc", "TrimSpace", "TrimSuffix", + ]) and + nodeFrom = cn.getArgument(0) and + nodeTo = cn.getAResult() ) ) and stateFrom instanceof PreValidation and From 1499372df08a2485c9a7d2a9661d3151ac9d91f5 Mon Sep 17 00:00:00 2001 From: Sim4n6 Date: Tue, 30 May 2023 19:41:53 +0100 Subject: [PATCH 18/36] use of sink instanceof Sink --- .../go/security/UnicodeBypassValidationQuery.qll | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/go/ql/lib/semmle/go/security/UnicodeBypassValidationQuery.qll b/go/ql/lib/semmle/go/security/UnicodeBypassValidationQuery.qll index e59a6e92df36..f7973ccb4ff7 100644 --- a/go/ql/lib/semmle/go/security/UnicodeBypassValidationQuery.qll +++ b/go/ql/lib/semmle/go/security/UnicodeBypassValidationQuery.qll @@ -101,10 +101,14 @@ class Configuration extends TaintTracking::Configuration { /* A Unicode Tranformation (Unicode tranformation) is considered a sink when the algorithm used is either NFC or NFKC. */ override predicate isSink(DataFlow::Node sink, DataFlow::FlowState state) { - exists(string unicodeNorm, DataFlow::MethodCallNode cn | - unicodeNorm = package("golang.org/x/text", "unicode/norm") and - cn.getTarget().hasQualifiedName(unicodeNorm, "Form", "String") and - sink = cn.getArgument(0) + ( + exists(string unicodeNorm, DataFlow::MethodCallNode cn | + unicodeNorm = package("golang.org/x/text", "unicode/norm") and + cn.getTarget().hasQualifiedName(unicodeNorm, "Form", "String") and + sink = cn.getArgument(0) + ) + or + sink instanceof Sink ) and state instanceof PostValidation } From e88508088ec91f5c9b75d0150cfeeb2ab42019b2 Mon Sep 17 00:00:00 2001 From: Sim4n6 Date: Thu, 1 Jun 2023 11:56:42 +0100 Subject: [PATCH 19/36] Add test using strings.Index() --- go/ql/test/experimental/CWE-176/example_bad.go | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/go/ql/test/experimental/CWE-176/example_bad.go b/go/ql/test/experimental/CWE-176/example_bad.go index d6dbd0a5276b..fd4107db41fe 100644 --- a/go/ql/test/experimental/CWE-176/example_bad.go +++ b/go/ql/test/experimental/CWE-176/example_bad.go @@ -4,6 +4,7 @@ import ( "fmt" "html" "net/http" + "strings" "golang.org/x/text/unicode/norm" ) @@ -11,11 +12,23 @@ import ( func main() {} func bad() { - http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { + http.HandleFunc("/bad1", func(w http.ResponseWriter, req *http.Request) { unicode_input := req.URL.Query().Get("unicode_input") escaped := html.EscapeString(unicode_input) unicode_norm := norm.NFKC.String(escaped) // $result=BAD fmt.Println(w, "Results: %q", unicode_norm) }) + + http.HandleFunc("/bad2", func(w http.ResponseWriter, req *http.Request) { + + unicode_input := req.URL.Query().Get("unicode_input") + escaped := html.EscapeString(unicode_input) + if strings.Index(escaped, "<") == -1 { + unicode_norm := norm.NFKC.String(escaped) // $result=BAD + fmt.Println(w, "Results: %q", unicode_norm) + } + + }) + } From 8dec3a5da073acec749b640055f0cfdf2328b090 Mon Sep 17 00:00:00 2001 From: Sim4n6 Date: Thu, 1 Jun 2023 11:57:33 +0100 Subject: [PATCH 20/36] Consider the guard of Untrusted Unicode Chararacters with some checks --- .../security/UnicodeBypassValidationQuery.qll | 43 +++++++++++++------ 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/go/ql/lib/semmle/go/security/UnicodeBypassValidationQuery.qll b/go/ql/lib/semmle/go/security/UnicodeBypassValidationQuery.qll index f7973ccb4ff7..65be20eaf280 100644 --- a/go/ql/lib/semmle/go/security/UnicodeBypassValidationQuery.qll +++ b/go/ql/lib/semmle/go/security/UnicodeBypassValidationQuery.qll @@ -18,6 +18,29 @@ class PostValidation extends DataFlow::FlowState { PostValidation() { this = "PostValidation" } } +private predicate untrustedUnicodeCharCheckGuard(DataFlow::Node cn, Expr e, boolean outcome) { + cn.(DataFlow::CallNode) + .getTarget() + .hasQualifiedName("strings", + [ + "Contains", "ContainsAny", "ContainsRune", "Count", "EqualFold", "HasPrefix", "HasSuffix", + "Index", "IndexAny", "IndexByte", "IndexFunc", "IndexRune", "LastIndex", "LastIndexAny", + "LastIndexByte", "LastIndexFunc", + ]) and + cn.(DataFlow::CallNode).getArgument(0).asExpr() = e and + outcome = true +} + +/** + * A call to a function called `Index`, `ContainsAny`, or similar, which may be + * considered a barrier guard for eliminating untrusted Unicode characters. + */ +class UntrustedUnicodeCharCheckBarrier extends DataFlow::Node { + UntrustedUnicodeCharCheckBarrier() { + this = DataFlow::BarrierGuard ::getABarrierNode() + } +} + /** * A taint-tracking configuration for detecting "Unicode transformation mishandling" vulnerabilities. * @@ -32,6 +55,14 @@ class Configuration extends TaintTracking::Configuration { state instanceof PreValidation } + override predicate isSanitizer(DataFlow::Node sanitizer, DataFlow::FlowState state) { + ( + sanitizer instanceof UntrustedUnicodeCharCheckBarrier or + sanitizer instanceof Sanitizer + ) and + state instanceof PreValidation + } + override predicate isAdditionalTaintStep( DataFlow::Node nodeFrom, DataFlow::FlowState stateFrom, DataFlow::Node nodeTo, DataFlow::FlowState stateTo @@ -52,18 +83,6 @@ class Configuration extends TaintTracking::Configuration { nodeTo = cn.getResult() ) or - exists(DataFlow::CallNode cn | - cn.getTarget() - .hasQualifiedName("strings", - [ - "Contains", "ContainsAny", "ContainsRune", "Count", "EqualFold", "HasPrefix", - "HasSuffix", "Index", "IndexAny", "IndexByte", "IndexFunc", "IndexRune", - "LastIndex", "LastIndexAny", "LastIndexByte", "LastIndexFunc", - ]) and - nodeFrom = cn.getArgument(0) and - nodeTo = nodeFrom - ) - or exists(DataFlow::CallNode cn | cn.getTarget().hasQualifiedName("strings", "Compare") and nodeFrom = cn.getAnArgument() and From 63bd853f1c7d2b085e74bda4fca57d3b1f9b6187 Mon Sep 17 00:00:00 2001 From: Sim4n6 Date: Thu, 1 Jun 2023 11:57:51 +0100 Subject: [PATCH 21/36] Update exptected file --- .../CWE-176/UnicodeBypassValidation.expected | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/go/ql/test/experimental/CWE-176/UnicodeBypassValidation.expected b/go/ql/test/experimental/CWE-176/UnicodeBypassValidation.expected index 5884c483b420..504290132bb0 100644 --- a/go/ql/test/experimental/CWE-176/UnicodeBypassValidation.expected +++ b/go/ql/test/experimental/CWE-176/UnicodeBypassValidation.expected @@ -1,16 +1,28 @@ edges -| example_bad.go:16:20:16:26 | selection of URL | example_bad.go:16:20:16:34 | call to Query | -| example_bad.go:16:20:16:34 | call to Query | example_bad.go:16:20:16:55 | call to Get | -| example_bad.go:16:20:16:55 | call to Get | example_bad.go:17:32:17:44 | unicode_input | -| example_bad.go:17:14:17:45 | call to EscapeString | example_bad.go:18:36:18:42 | escaped | -| example_bad.go:17:32:17:44 | unicode_input | example_bad.go:17:14:17:45 | call to EscapeString | +| example_bad.go:17:20:17:26 | selection of URL | example_bad.go:17:20:17:34 | call to Query | +| example_bad.go:17:20:17:34 | call to Query | example_bad.go:17:20:17:55 | call to Get | +| example_bad.go:17:20:17:55 | call to Get | example_bad.go:18:32:18:44 | unicode_input | +| example_bad.go:18:14:18:45 | call to EscapeString | example_bad.go:19:36:19:42 | escaped | +| example_bad.go:18:32:18:44 | unicode_input | example_bad.go:18:14:18:45 | call to EscapeString | +| example_bad.go:25:20:25:26 | selection of URL | example_bad.go:25:20:25:34 | call to Query | +| example_bad.go:25:20:25:34 | call to Query | example_bad.go:25:20:25:55 | call to Get | +| example_bad.go:25:20:25:55 | call to Get | example_bad.go:26:32:26:44 | unicode_input | +| example_bad.go:26:14:26:45 | call to EscapeString | example_bad.go:28:37:28:43 | escaped | +| example_bad.go:26:32:26:44 | unicode_input | example_bad.go:26:14:26:45 | call to EscapeString | nodes -| example_bad.go:16:20:16:26 | selection of URL | semmle.label | selection of URL | -| example_bad.go:16:20:16:34 | call to Query | semmle.label | call to Query | -| example_bad.go:16:20:16:55 | call to Get | semmle.label | call to Get | -| example_bad.go:17:14:17:45 | call to EscapeString | semmle.label | call to EscapeString | -| example_bad.go:17:32:17:44 | unicode_input | semmle.label | unicode_input | -| example_bad.go:18:36:18:42 | escaped | semmle.label | escaped | +| example_bad.go:17:20:17:26 | selection of URL | semmle.label | selection of URL | +| example_bad.go:17:20:17:34 | call to Query | semmle.label | call to Query | +| example_bad.go:17:20:17:55 | call to Get | semmle.label | call to Get | +| example_bad.go:18:14:18:45 | call to EscapeString | semmle.label | call to EscapeString | +| example_bad.go:18:32:18:44 | unicode_input | semmle.label | unicode_input | +| example_bad.go:19:36:19:42 | escaped | semmle.label | escaped | +| example_bad.go:25:20:25:26 | selection of URL | semmle.label | selection of URL | +| example_bad.go:25:20:25:34 | call to Query | semmle.label | call to Query | +| example_bad.go:25:20:25:55 | call to Get | semmle.label | call to Get | +| example_bad.go:26:14:26:45 | call to EscapeString | semmle.label | call to EscapeString | +| example_bad.go:26:32:26:44 | unicode_input | semmle.label | unicode_input | +| example_bad.go:28:37:28:43 | escaped | semmle.label | escaped | subpaths #select -| example_bad.go:18:36:18:42 | escaped | example_bad.go:16:20:16:26 | selection of URL | example_bad.go:18:36:18:42 | escaped | This $@ processes unsafely $@ and any logical validation in-between could be bypassed using special Unicode characters. | example_bad.go:18:36:18:42 | escaped | Unicode transformation (Unicode normalization) | example_bad.go:16:20:16:26 | selection of URL | remote user-controlled data | +| example_bad.go:19:36:19:42 | escaped | example_bad.go:17:20:17:26 | selection of URL | example_bad.go:19:36:19:42 | escaped | This $@ processes unsafely $@ and any logical validation in-between could be bypassed using special Unicode characters. | example_bad.go:19:36:19:42 | escaped | Unicode transformation (Unicode normalization) | example_bad.go:17:20:17:26 | selection of URL | remote user-controlled data | +| example_bad.go:28:37:28:43 | escaped | example_bad.go:25:20:25:26 | selection of URL | example_bad.go:28:37:28:43 | escaped | This $@ processes unsafely $@ and any logical validation in-between could be bypassed using special Unicode characters. | example_bad.go:28:37:28:43 | escaped | Unicode transformation (Unicode normalization) | example_bad.go:25:20:25:26 | selection of URL | remote user-controlled data | From 2971c2a709946bdf4334fa2b89826bd05c0263e1 Mon Sep 17 00:00:00 2001 From: Sim4n6 Date: Tue, 6 Jun 2023 17:01:13 +0100 Subject: [PATCH 22/36] Use of intCheck and boolCheck as barrierGuards --- .../security/UnicodeBypassValidationQuery.qll | 38 +++++++++++++------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/go/ql/lib/semmle/go/security/UnicodeBypassValidationQuery.qll b/go/ql/lib/semmle/go/security/UnicodeBypassValidationQuery.qll index 65be20eaf280..14ffab94b9b4 100644 --- a/go/ql/lib/semmle/go/security/UnicodeBypassValidationQuery.qll +++ b/go/ql/lib/semmle/go/security/UnicodeBypassValidationQuery.qll @@ -18,26 +18,40 @@ class PostValidation extends DataFlow::FlowState { PostValidation() { this = "PostValidation" } } -private predicate untrustedUnicodeCharCheckGuard(DataFlow::Node cn, Expr e, boolean outcome) { +private predicate intCheck(DataFlow::Node g, Expr e, boolean outcome) { + exists(DataFlow::CallNode cn, DataFlow::EqualityTestNode etn | + g = etn and + DataFlow::localFlow(cn.getResult(), etn.getAnOperand()) and + cn.getTarget() + .hasQualifiedName("strings", + [ + "Index", "IndexAny", "IndexByte", "IndexFunc", "IndexRune", "LastIndex", "LastIndexAny", + "LastIndexByte", "LastIndexFunc", "Count", + ]) and + cn.getArgument(0).asExpr() = e and + etn.getAnOperand().getIntValue() = -1 and + outcome = etn.getPolarity() + ) +} + +private predicate boolCheck(DataFlow::Node cn, Expr e, boolean outcome) { cn.(DataFlow::CallNode) .getTarget() .hasQualifiedName("strings", - [ - "Contains", "ContainsAny", "ContainsRune", "Count", "EqualFold", "HasPrefix", "HasSuffix", - "Index", "IndexAny", "IndexByte", "IndexFunc", "IndexRune", "LastIndex", "LastIndexAny", - "LastIndexByte", "LastIndexFunc", - ]) and + ["Contains", "ContainsAny", "ContainsRune", "HasPrefix", "HasSuffix", "EqualFold"]) and cn.(DataFlow::CallNode).getArgument(0).asExpr() = e and - outcome = true + outcome = false } /** * A call to a function called `Index`, `ContainsAny`, or similar, which may be * considered a barrier guard for eliminating untrusted Unicode characters. */ -class UntrustedUnicodeCharCheckBarrier extends DataFlow::Node { - UntrustedUnicodeCharCheckBarrier() { - this = DataFlow::BarrierGuard ::getABarrierNode() +class UntrustedUnicodeCharChecks extends DataFlow::Node { + UntrustedUnicodeCharChecks() { + this = DataFlow::BarrierGuard ::getABarrierNode() + or + this = DataFlow::BarrierGuard ::getABarrierNode() } } @@ -57,10 +71,10 @@ class Configuration extends TaintTracking::Configuration { override predicate isSanitizer(DataFlow::Node sanitizer, DataFlow::FlowState state) { ( - sanitizer instanceof UntrustedUnicodeCharCheckBarrier or + sanitizer instanceof UntrustedUnicodeCharChecks or sanitizer instanceof Sanitizer ) and - state instanceof PreValidation + state instanceof PostValidation } override predicate isAdditionalTaintStep( From a9650d851ee4414a4730a48264b7397e7c0d1a51 Mon Sep 17 00:00:00 2001 From: Sim4n6 Date: Tue, 6 Jun 2023 17:01:40 +0100 Subject: [PATCH 23/36] Added two Good test samples --- go/ql/test/experimental/CWE-176/example_bad.go | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/go/ql/test/experimental/CWE-176/example_bad.go b/go/ql/test/experimental/CWE-176/example_bad.go index fd4107db41fe..db0b9355e98e 100644 --- a/go/ql/test/experimental/CWE-176/example_bad.go +++ b/go/ql/test/experimental/CWE-176/example_bad.go @@ -20,12 +20,21 @@ func bad() { fmt.Println(w, "Results: %q", unicode_norm) }) - http.HandleFunc("/bad2", func(w http.ResponseWriter, req *http.Request) { + http.HandleFunc("/good1_for_lt", func(w http.ResponseWriter, req *http.Request) { unicode_input := req.URL.Query().Get("unicode_input") - escaped := html.EscapeString(unicode_input) - if strings.Index(escaped, "<") == -1 { - unicode_norm := norm.NFKC.String(escaped) // $result=BAD + if strings.IndexAny(unicode_input, "<﹤<") == -1 { + unicode_norm := norm.NFKC.String(unicode_input) // $result=OK + fmt.Println(w, "Results: %q", unicode_norm) + } + + }) + + http.HandleFunc("/good2_for_lt", func(w http.ResponseWriter, req *http.Request) { + + unicode_input := req.URL.Query().Get("unicode_input") + if !strings.ContainsAny(unicode_input, "<﹤<") { + unicode_norm := norm.NFKC.String(unicode_input) // $result=OK fmt.Println(w, "Results: %q", unicode_norm) } From ece84b50e8bf1aa2bebd305a0e4179b50830d72b Mon Sep 17 00:00:00 2001 From: Sim4n6 Date: Tue, 6 Jun 2023 17:01:50 +0100 Subject: [PATCH 24/36] Updated the expected file --- .../CWE-176/UnicodeBypassValidation.expected | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/go/ql/test/experimental/CWE-176/UnicodeBypassValidation.expected b/go/ql/test/experimental/CWE-176/UnicodeBypassValidation.expected index 504290132bb0..47b065422ae5 100644 --- a/go/ql/test/experimental/CWE-176/UnicodeBypassValidation.expected +++ b/go/ql/test/experimental/CWE-176/UnicodeBypassValidation.expected @@ -4,11 +4,6 @@ edges | example_bad.go:17:20:17:55 | call to Get | example_bad.go:18:32:18:44 | unicode_input | | example_bad.go:18:14:18:45 | call to EscapeString | example_bad.go:19:36:19:42 | escaped | | example_bad.go:18:32:18:44 | unicode_input | example_bad.go:18:14:18:45 | call to EscapeString | -| example_bad.go:25:20:25:26 | selection of URL | example_bad.go:25:20:25:34 | call to Query | -| example_bad.go:25:20:25:34 | call to Query | example_bad.go:25:20:25:55 | call to Get | -| example_bad.go:25:20:25:55 | call to Get | example_bad.go:26:32:26:44 | unicode_input | -| example_bad.go:26:14:26:45 | call to EscapeString | example_bad.go:28:37:28:43 | escaped | -| example_bad.go:26:32:26:44 | unicode_input | example_bad.go:26:14:26:45 | call to EscapeString | nodes | example_bad.go:17:20:17:26 | selection of URL | semmle.label | selection of URL | | example_bad.go:17:20:17:34 | call to Query | semmle.label | call to Query | @@ -16,13 +11,6 @@ nodes | example_bad.go:18:14:18:45 | call to EscapeString | semmle.label | call to EscapeString | | example_bad.go:18:32:18:44 | unicode_input | semmle.label | unicode_input | | example_bad.go:19:36:19:42 | escaped | semmle.label | escaped | -| example_bad.go:25:20:25:26 | selection of URL | semmle.label | selection of URL | -| example_bad.go:25:20:25:34 | call to Query | semmle.label | call to Query | -| example_bad.go:25:20:25:55 | call to Get | semmle.label | call to Get | -| example_bad.go:26:14:26:45 | call to EscapeString | semmle.label | call to EscapeString | -| example_bad.go:26:32:26:44 | unicode_input | semmle.label | unicode_input | -| example_bad.go:28:37:28:43 | escaped | semmle.label | escaped | subpaths #select | example_bad.go:19:36:19:42 | escaped | example_bad.go:17:20:17:26 | selection of URL | example_bad.go:19:36:19:42 | escaped | This $@ processes unsafely $@ and any logical validation in-between could be bypassed using special Unicode characters. | example_bad.go:19:36:19:42 | escaped | Unicode transformation (Unicode normalization) | example_bad.go:17:20:17:26 | selection of URL | remote user-controlled data | -| example_bad.go:28:37:28:43 | escaped | example_bad.go:25:20:25:26 | selection of URL | example_bad.go:28:37:28:43 | escaped | This $@ processes unsafely $@ and any logical validation in-between could be bypassed using special Unicode characters. | example_bad.go:28:37:28:43 | escaped | Unicode transformation (Unicode normalization) | example_bad.go:25:20:25:26 | selection of URL | remote user-controlled data | From 92f3f837b1d5d684c7005bd7a397785f0b8898f2 Mon Sep 17 00:00:00 2001 From: Sim4n6 Date: Tue, 13 Jun 2023 12:25:18 +0100 Subject: [PATCH 25/36] Added a missing bad example --- go/ql/test/experimental/CWE-176/example_bad.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/go/ql/test/experimental/CWE-176/example_bad.go b/go/ql/test/experimental/CWE-176/example_bad.go index db0b9355e98e..2aaeb8a92e99 100644 --- a/go/ql/test/experimental/CWE-176/example_bad.go +++ b/go/ql/test/experimental/CWE-176/example_bad.go @@ -40,4 +40,14 @@ func bad() { }) + http.HandleFunc("/bad2", func(w http.ResponseWriter, req *http.Request) { + + unicode_input := req.URL.Query().Get("unicode_input") + if !strings.Contains(unicode_input, "<") { + unicode_norm := norm.NFKC.String(unicode_input) // $Missing:result=BAD + fmt.Println(w, "Results: %q", unicode_norm) + } + + }) + } From c9c6054a3465a9a10318a80a01659869b7c12923 Mon Sep 17 00:00:00 2001 From: Sim4n6 Date: Tue, 13 Jun 2023 12:37:53 +0100 Subject: [PATCH 26/36] UntrustedUnicodeCharCheckBarrier is part of Sink, all samples are BAD --- .../go/security/UnicodeBypassValidationQuery.qll | 11 ++++++----- go/ql/test/experimental/CWE-176/example_bad.go | 12 ++++++------ 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/go/ql/lib/semmle/go/security/UnicodeBypassValidationQuery.qll b/go/ql/lib/semmle/go/security/UnicodeBypassValidationQuery.qll index 14ffab94b9b4..a774988b3dba 100644 --- a/go/ql/lib/semmle/go/security/UnicodeBypassValidationQuery.qll +++ b/go/ql/lib/semmle/go/security/UnicodeBypassValidationQuery.qll @@ -70,10 +70,7 @@ class Configuration extends TaintTracking::Configuration { } override predicate isSanitizer(DataFlow::Node sanitizer, DataFlow::FlowState state) { - ( - sanitizer instanceof UntrustedUnicodeCharChecks or - sanitizer instanceof Sanitizer - ) and + sanitizer instanceof Sanitizer and state instanceof PostValidation } @@ -143,6 +140,10 @@ class Configuration extends TaintTracking::Configuration { or sink instanceof Sink ) and - state instanceof PostValidation + ( + state instanceof PostValidation + or + sink instanceof UntrustedUnicodeCharChecks and state instanceof PreValidation + ) } } diff --git a/go/ql/test/experimental/CWE-176/example_bad.go b/go/ql/test/experimental/CWE-176/example_bad.go index 2aaeb8a92e99..657d79c7d127 100644 --- a/go/ql/test/experimental/CWE-176/example_bad.go +++ b/go/ql/test/experimental/CWE-176/example_bad.go @@ -20,31 +20,31 @@ func bad() { fmt.Println(w, "Results: %q", unicode_norm) }) - http.HandleFunc("/good1_for_lt", func(w http.ResponseWriter, req *http.Request) { + http.HandleFunc("/bad2", func(w http.ResponseWriter, req *http.Request) { unicode_input := req.URL.Query().Get("unicode_input") if strings.IndexAny(unicode_input, "<﹤<") == -1 { - unicode_norm := norm.NFKC.String(unicode_input) // $result=OK + unicode_norm := norm.NFKC.String(unicode_input) // $result=BAD fmt.Println(w, "Results: %q", unicode_norm) } }) - http.HandleFunc("/good2_for_lt", func(w http.ResponseWriter, req *http.Request) { + http.HandleFunc("/bad3", func(w http.ResponseWriter, req *http.Request) { unicode_input := req.URL.Query().Get("unicode_input") if !strings.ContainsAny(unicode_input, "<﹤<") { - unicode_norm := norm.NFKC.String(unicode_input) // $result=OK + unicode_norm := norm.NFKC.String(unicode_input) // $result=BAD fmt.Println(w, "Results: %q", unicode_norm) } }) - http.HandleFunc("/bad2", func(w http.ResponseWriter, req *http.Request) { + http.HandleFunc("/bad4", func(w http.ResponseWriter, req *http.Request) { unicode_input := req.URL.Query().Get("unicode_input") if !strings.Contains(unicode_input, "<") { - unicode_norm := norm.NFKC.String(unicode_input) // $Missing:result=BAD + unicode_norm := norm.NFKC.String(unicode_input) // $result=BAD fmt.Println(w, "Results: %q", unicode_norm) } From 7f42af5d9058cdf15102aa70a4d2148680209243 Mon Sep 17 00:00:00 2001 From: Sim4n6 Date: Tue, 13 Jun 2023 12:39:35 +0100 Subject: [PATCH 27/36] Update expected test results --- .../CWE-176/UnicodeBypassValidation.expected | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/go/ql/test/experimental/CWE-176/UnicodeBypassValidation.expected b/go/ql/test/experimental/CWE-176/UnicodeBypassValidation.expected index 47b065422ae5..639967f4a6af 100644 --- a/go/ql/test/experimental/CWE-176/UnicodeBypassValidation.expected +++ b/go/ql/test/experimental/CWE-176/UnicodeBypassValidation.expected @@ -4,6 +4,15 @@ edges | example_bad.go:17:20:17:55 | call to Get | example_bad.go:18:32:18:44 | unicode_input | | example_bad.go:18:14:18:45 | call to EscapeString | example_bad.go:19:36:19:42 | escaped | | example_bad.go:18:32:18:44 | unicode_input | example_bad.go:18:14:18:45 | call to EscapeString | +| example_bad.go:25:20:25:26 | selection of URL | example_bad.go:25:20:25:34 | call to Query | +| example_bad.go:25:20:25:34 | call to Query | example_bad.go:25:20:25:55 | call to Get | +| example_bad.go:25:20:25:55 | call to Get | example_bad.go:27:37:27:49 | unicode_input | +| example_bad.go:35:20:35:26 | selection of URL | example_bad.go:35:20:35:34 | call to Query | +| example_bad.go:35:20:35:34 | call to Query | example_bad.go:35:20:35:55 | call to Get | +| example_bad.go:35:20:35:55 | call to Get | example_bad.go:37:37:37:49 | unicode_input | +| example_bad.go:45:20:45:26 | selection of URL | example_bad.go:45:20:45:34 | call to Query | +| example_bad.go:45:20:45:34 | call to Query | example_bad.go:45:20:45:55 | call to Get | +| example_bad.go:45:20:45:55 | call to Get | example_bad.go:47:37:47:49 | unicode_input | nodes | example_bad.go:17:20:17:26 | selection of URL | semmle.label | selection of URL | | example_bad.go:17:20:17:34 | call to Query | semmle.label | call to Query | @@ -11,6 +20,21 @@ nodes | example_bad.go:18:14:18:45 | call to EscapeString | semmle.label | call to EscapeString | | example_bad.go:18:32:18:44 | unicode_input | semmle.label | unicode_input | | example_bad.go:19:36:19:42 | escaped | semmle.label | escaped | +| example_bad.go:25:20:25:26 | selection of URL | semmle.label | selection of URL | +| example_bad.go:25:20:25:34 | call to Query | semmle.label | call to Query | +| example_bad.go:25:20:25:55 | call to Get | semmle.label | call to Get | +| example_bad.go:27:37:27:49 | unicode_input | semmle.label | unicode_input | +| example_bad.go:35:20:35:26 | selection of URL | semmle.label | selection of URL | +| example_bad.go:35:20:35:34 | call to Query | semmle.label | call to Query | +| example_bad.go:35:20:35:55 | call to Get | semmle.label | call to Get | +| example_bad.go:37:37:37:49 | unicode_input | semmle.label | unicode_input | +| example_bad.go:45:20:45:26 | selection of URL | semmle.label | selection of URL | +| example_bad.go:45:20:45:34 | call to Query | semmle.label | call to Query | +| example_bad.go:45:20:45:55 | call to Get | semmle.label | call to Get | +| example_bad.go:47:37:47:49 | unicode_input | semmle.label | unicode_input | subpaths #select | example_bad.go:19:36:19:42 | escaped | example_bad.go:17:20:17:26 | selection of URL | example_bad.go:19:36:19:42 | escaped | This $@ processes unsafely $@ and any logical validation in-between could be bypassed using special Unicode characters. | example_bad.go:19:36:19:42 | escaped | Unicode transformation (Unicode normalization) | example_bad.go:17:20:17:26 | selection of URL | remote user-controlled data | +| example_bad.go:27:37:27:49 | unicode_input | example_bad.go:25:20:25:26 | selection of URL | example_bad.go:27:37:27:49 | unicode_input | This $@ processes unsafely $@ and any logical validation in-between could be bypassed using special Unicode characters. | example_bad.go:27:37:27:49 | unicode_input | Unicode transformation (Unicode normalization) | example_bad.go:25:20:25:26 | selection of URL | remote user-controlled data | +| example_bad.go:37:37:37:49 | unicode_input | example_bad.go:35:20:35:26 | selection of URL | example_bad.go:37:37:37:49 | unicode_input | This $@ processes unsafely $@ and any logical validation in-between could be bypassed using special Unicode characters. | example_bad.go:37:37:37:49 | unicode_input | Unicode transformation (Unicode normalization) | example_bad.go:35:20:35:26 | selection of URL | remote user-controlled data | +| example_bad.go:47:37:47:49 | unicode_input | example_bad.go:45:20:45:26 | selection of URL | example_bad.go:47:37:47:49 | unicode_input | This $@ processes unsafely $@ and any logical validation in-between could be bypassed using special Unicode characters. | example_bad.go:47:37:47:49 | unicode_input | Unicode transformation (Unicode normalization) | example_bad.go:45:20:45:26 | selection of URL | remote user-controlled data | From 3ad70880c0173164690bb82b42494f54829d0e70 Mon Sep 17 00:00:00 2001 From: Sim4n6 Date: Mon, 26 Jun 2023 11:00:35 +0100 Subject: [PATCH 28/36] Updated the test cases with more --- .../CWE-176/UnicodeBypassValidation.go | 81 +++++++++++++++++++ .../test/experimental/CWE-176/example_bad.go | 53 ------------ 2 files changed, 81 insertions(+), 53 deletions(-) create mode 100644 go/ql/test/experimental/CWE-176/UnicodeBypassValidation.go delete mode 100644 go/ql/test/experimental/CWE-176/example_bad.go diff --git a/go/ql/test/experimental/CWE-176/UnicodeBypassValidation.go b/go/ql/test/experimental/CWE-176/UnicodeBypassValidation.go new file mode 100644 index 000000000000..e90c0dd7707e --- /dev/null +++ b/go/ql/test/experimental/CWE-176/UnicodeBypassValidation.go @@ -0,0 +1,81 @@ +package main + +import ( + "fmt" + "html" + "net/http" + "strings" + "regexp" + + "golang.org/x/text/unicode/norm" +) + +func main() {} + +func bad() { + http.HandleFunc("/bad_1", func(w http.ResponseWriter, req *http.Request) { + // BAD: Unicode normalization is applied after escaping. + unicode_input := req.URL.Query().Get("unicode_input") + escaped := html.EscapeString(unicode_input) + unicode_norm := norm.NFKC.String(escaped) // $result=BAD + fmt.Println(w, "Results: %q", unicode_norm) + }) + + http.HandleFunc("/good_1", func(w http.ResponseWriter, req *http.Request) { + // GOOD: Unicode normalization is applied before escaping. + unicode_input := req.URL.Query().Get("unicode_input") + unicode_norm := norm.NFKC.String(unicode_input) // $result=OK + unicode_escaped := html.EscapeString(unicode_norm) + + fmt.Println(w, "Results: %q", unicode_escaped) + }) + + http.HandleFunc("/bad_2", func(w http.ResponseWriter, req *http.Request) { + + // BAD: Unicode normalization is performed after checking + // if any of the unsafe two characters "<<" is present, there may + // be more like the tricky "﹤" + unicode_input := req.URL.Query().Get("unicode_input") + ind := strings.IndexAny(unicode_input, "<<") + if ind == -1 { + unicode_norm := norm.NFKC.String(unicode_input) // $result=BAD + fmt.Println(w, "Results: %q", unicode_norm) + } else { + fmt.Println(w, "Potential unsafe characters used : %q", unicode_input) + } + + }) + + http.HandleFunc("/bad_3", func(w http.ResponseWriter, req *http.Request) { + + // BAD: Unicode normalization is guarded by the call to `ContainsAny()` which suggests that the + // input has been validated against the presence of the unsafe characters "<" or their unicode + // equivalents. This may not the be the case of all the possible Unicode equivalents. + unicode_input := req.URL.Query().Get("unicode_input") + if !strings.ContainsAny(unicode_input, "<﹤<") { + unicode_norm := norm.NFKC.String(unicode_input) // $result=BAD + fmt.Println(w, "Results: %q", unicode_norm) + } else { + fmt.Println(w, "Contains unsafe characters: %q", unicode_input) + } + + }) + + http.HandleFunc("/bad_4", func(w http.ResponseWriter, req *http.Request) { + + // BAD: Unicode normalization is performed after the regex match validation is performed + // against the unsafe characters "<" and ">". This may be bypassed using the Unicode characters + // equivalent to "<" and ">". + unicode_input := req.URL.Query().Get("unicode_input") + re := regexp.MustCompile("[<>]") + if !re.MatchString(unicode_input) { + unicode_norm := norm.NFKC.String(unicode_input) // $result=BAD + fmt.Println(w, "Results: %q", unicode_norm) + } else { + fmt.Println("The input is not safe.") + } + + }) + +} +u \ No newline at end of file diff --git a/go/ql/test/experimental/CWE-176/example_bad.go b/go/ql/test/experimental/CWE-176/example_bad.go deleted file mode 100644 index 657d79c7d127..000000000000 --- a/go/ql/test/experimental/CWE-176/example_bad.go +++ /dev/null @@ -1,53 +0,0 @@ -package main - -import ( - "fmt" - "html" - "net/http" - "strings" - - "golang.org/x/text/unicode/norm" -) - -func main() {} - -func bad() { - http.HandleFunc("/bad1", func(w http.ResponseWriter, req *http.Request) { - - unicode_input := req.URL.Query().Get("unicode_input") - escaped := html.EscapeString(unicode_input) - unicode_norm := norm.NFKC.String(escaped) // $result=BAD - fmt.Println(w, "Results: %q", unicode_norm) - }) - - http.HandleFunc("/bad2", func(w http.ResponseWriter, req *http.Request) { - - unicode_input := req.URL.Query().Get("unicode_input") - if strings.IndexAny(unicode_input, "<﹤<") == -1 { - unicode_norm := norm.NFKC.String(unicode_input) // $result=BAD - fmt.Println(w, "Results: %q", unicode_norm) - } - - }) - - http.HandleFunc("/bad3", func(w http.ResponseWriter, req *http.Request) { - - unicode_input := req.URL.Query().Get("unicode_input") - if !strings.ContainsAny(unicode_input, "<﹤<") { - unicode_norm := norm.NFKC.String(unicode_input) // $result=BAD - fmt.Println(w, "Results: %q", unicode_norm) - } - - }) - - http.HandleFunc("/bad4", func(w http.ResponseWriter, req *http.Request) { - - unicode_input := req.URL.Query().Get("unicode_input") - if !strings.Contains(unicode_input, "<") { - unicode_norm := norm.NFKC.String(unicode_input) // $result=BAD - fmt.Println(w, "Results: %q", unicode_norm) - } - - }) - -} From 61959d454ba9c1547fab99dc38a752d4b8b0406b Mon Sep 17 00:00:00 2001 From: Sim4n6 Date: Mon, 26 Jun 2023 11:03:45 +0100 Subject: [PATCH 29/36] Update go/ql/lib/semmle/go/security/UnicodeBypassValidationQuery.qll Co-authored-by: Chris Smowton --- go/ql/lib/semmle/go/security/UnicodeBypassValidationQuery.qll | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/go/ql/lib/semmle/go/security/UnicodeBypassValidationQuery.qll b/go/ql/lib/semmle/go/security/UnicodeBypassValidationQuery.qll index a774988b3dba..8f3dadb32086 100644 --- a/go/ql/lib/semmle/go/security/UnicodeBypassValidationQuery.qll +++ b/go/ql/lib/semmle/go/security/UnicodeBypassValidationQuery.qll @@ -44,8 +44,8 @@ private predicate boolCheck(DataFlow::Node cn, Expr e, boolean outcome) { } /** - * A call to a function called `Index`, `ContainsAny`, or similar, which may be - * considered a barrier guard for eliminating untrusted Unicode characters. + * A use of a variable guarded by a call to `Index`, `ContainsAny`, or similar, in a context + * suggesting it has been validated to not contain a particular character. */ class UntrustedUnicodeCharChecks extends DataFlow::Node { UntrustedUnicodeCharChecks() { From a64a998981a70f2c4846a222856143874f0e9852 Mon Sep 17 00:00:00 2001 From: Sim4n6 Date: Mon, 26 Jun 2023 11:09:31 +0100 Subject: [PATCH 30/36] Regex match function calls could be considered a barrier guard checks --- .../security/UnicodeBypassValidationQuery.qll | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/go/ql/lib/semmle/go/security/UnicodeBypassValidationQuery.qll b/go/ql/lib/semmle/go/security/UnicodeBypassValidationQuery.qll index 8f3dadb32086..70bfd510f0ce 100644 --- a/go/ql/lib/semmle/go/security/UnicodeBypassValidationQuery.qll +++ b/go/ql/lib/semmle/go/security/UnicodeBypassValidationQuery.qll @@ -43,15 +43,23 @@ private predicate boolCheck(DataFlow::Node cn, Expr e, boolean outcome) { outcome = false } +private predicate regexMatchCheck(DataFlow::Node cn, Expr e, boolean outcome) { + cn.(DataFlow::CallNode).getTarget() instanceof RegexpMatchFunction and + cn.(DataFlow::CallNode).getArgument(0).asExpr() = e and + outcome = false +} + /** - * A use of a variable guarded by a call to `Index`, `ContainsAny`, or similar, in a context - * suggesting it has been validated to not contain a particular character. + * A use of a variable guarded by a call to `Index`, `ContainsAny`, Regex match functions + * or similar, in a context suggesting it has been validated to not contain a particular character. */ class UntrustedUnicodeCharChecks extends DataFlow::Node { UntrustedUnicodeCharChecks() { this = DataFlow::BarrierGuard ::getABarrierNode() or this = DataFlow::BarrierGuard ::getABarrierNode() + or + this = DataFlow::BarrierGuard ::getABarrierNode() } } @@ -86,10 +94,7 @@ class Configuration extends TaintTracking::Configuration { ) or exists(DataFlow::CallNode cn | - ( - cn.getACalleeIncludingExternals().asFunction() instanceof RegexpMatchFunction or - cn.getACalleeIncludingExternals().asFunction() instanceof RegexpReplaceFunction - ) and + cn.getACalleeIncludingExternals().asFunction() instanceof RegexpReplaceFunction and nodeFrom = cn.getAnArgument() and nodeTo = cn.getResult() ) From b3abf6c1d19e847f8b97f3b6cc8c70817eedf82c Mon Sep 17 00:00:00 2001 From: Sim4n6 Date: Mon, 26 Jun 2023 11:18:24 +0100 Subject: [PATCH 31/36] Comment in details. --- .../semmle/go/security/UnicodeBypassValidationQuery.qll | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/go/ql/lib/semmle/go/security/UnicodeBypassValidationQuery.qll b/go/ql/lib/semmle/go/security/UnicodeBypassValidationQuery.qll index 70bfd510f0ce..ee84c81e4be4 100644 --- a/go/ql/lib/semmle/go/security/UnicodeBypassValidationQuery.qll +++ b/go/ql/lib/semmle/go/security/UnicodeBypassValidationQuery.qll @@ -134,7 +134,12 @@ class Configuration extends TaintTracking::Configuration { stateTo instanceof PostValidation } - /* A Unicode Tranformation (Unicode tranformation) is considered a sink when the algorithm used is either NFC or NFKC. */ + /* + * A Unicode Tranformation is considered a sink when the form algorithm used is for Unicode normalization (NFC, NFKC, etc) and one of the two scenarios happens: + * - The flow went through a call to an Escape function, Regex Replace function, or a String manipulation function. + * - The Unicode normalisation was guarded by a check that the input does not contain a particular character either using Regex match functions or String checks functions like Index/Contains functions. + */ + override predicate isSink(DataFlow::Node sink, DataFlow::FlowState state) { ( exists(string unicodeNorm, DataFlow::MethodCallNode cn | From 2a22decce96b4b884e1964823b8a6b55dabeeca0 Mon Sep 17 00:00:00 2001 From: Sim4n6 Date: Mon, 26 Jun 2023 11:22:05 +0100 Subject: [PATCH 32/36] Use of compare calls as a check for barrier --- .../go/security/UnicodeBypassValidationQuery.qll | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/go/ql/lib/semmle/go/security/UnicodeBypassValidationQuery.qll b/go/ql/lib/semmle/go/security/UnicodeBypassValidationQuery.qll index ee84c81e4be4..552ec28161d9 100644 --- a/go/ql/lib/semmle/go/security/UnicodeBypassValidationQuery.qll +++ b/go/ql/lib/semmle/go/security/UnicodeBypassValidationQuery.qll @@ -43,6 +43,14 @@ private predicate boolCheck(DataFlow::Node cn, Expr e, boolean outcome) { outcome = false } +private predicate compareCheck(DataFlow::Node cn, Expr e, boolean outcome) { + cn.(DataFlow::CallNode) + .getTarget() + .hasQualifiedName("strings", ["Compare", "CompareFold", "ComparePrefix", "CompareSuffix"]) and + cn.(DataFlow::CallNode).getArgument(0).asExpr() = e and + outcome = false +} + private predicate regexMatchCheck(DataFlow::Node cn, Expr e, boolean outcome) { cn.(DataFlow::CallNode).getTarget() instanceof RegexpMatchFunction and cn.(DataFlow::CallNode).getArgument(0).asExpr() = e and @@ -60,6 +68,8 @@ class UntrustedUnicodeCharChecks extends DataFlow::Node { this = DataFlow::BarrierGuard ::getABarrierNode() or this = DataFlow::BarrierGuard ::getABarrierNode() + or + this = DataFlow::BarrierGuard ::getABarrierNode() } } @@ -99,12 +109,6 @@ class Configuration extends TaintTracking::Configuration { nodeTo = cn.getResult() ) or - exists(DataFlow::CallNode cn | - cn.getTarget().hasQualifiedName("strings", "Compare") and - nodeFrom = cn.getAnArgument() and - nodeTo = nodeFrom - ) - or exists(DataFlow::CallNode cn | cn.getTarget().hasQualifiedName("strings", "Cut") and nodeFrom = cn.getArgument(0) and From c2f723a3f6a7a80ccdf0202f8915fde9b0ef073e Mon Sep 17 00:00:00 2001 From: Sim4n6 Date: Mon, 26 Jun 2023 11:28:38 +0100 Subject: [PATCH 33/36] count check against zero --- .../security/UnicodeBypassValidationQuery.qll | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/go/ql/lib/semmle/go/security/UnicodeBypassValidationQuery.qll b/go/ql/lib/semmle/go/security/UnicodeBypassValidationQuery.qll index 552ec28161d9..da6721a50170 100644 --- a/go/ql/lib/semmle/go/security/UnicodeBypassValidationQuery.qll +++ b/go/ql/lib/semmle/go/security/UnicodeBypassValidationQuery.qll @@ -18,7 +18,7 @@ class PostValidation extends DataFlow::FlowState { PostValidation() { this = "PostValidation" } } -private predicate intCheck(DataFlow::Node g, Expr e, boolean outcome) { +private predicate indexCheck(DataFlow::Node g, Expr e, boolean outcome) { exists(DataFlow::CallNode cn, DataFlow::EqualityTestNode etn | g = etn and DataFlow::localFlow(cn.getResult(), etn.getAnOperand()) and @@ -26,7 +26,7 @@ private predicate intCheck(DataFlow::Node g, Expr e, boolean outcome) { .hasQualifiedName("strings", [ "Index", "IndexAny", "IndexByte", "IndexFunc", "IndexRune", "LastIndex", "LastIndexAny", - "LastIndexByte", "LastIndexFunc", "Count", + "LastIndexByte", "LastIndexFunc", ]) and cn.getArgument(0).asExpr() = e and etn.getAnOperand().getIntValue() = -1 and @@ -34,6 +34,18 @@ private predicate intCheck(DataFlow::Node g, Expr e, boolean outcome) { ) } +private predicate countCheck(DataFlow::Node g, Expr e, boolean outcome) { + exists(DataFlow::CallNode cn, DataFlow::EqualityTestNode etn | + g = etn and + DataFlow::localFlow(cn.getResult(), etn.getAnOperand()) and + cn.getTarget() + .hasQualifiedName("strings", ["Count", "CountAny", "CountByte", "CountFunc", "CountRune"]) and + cn.getArgument(0).asExpr() = e and + etn.getAnOperand().getIntValue() = 0 and + outcome = etn.getPolarity() + ) +} + private predicate boolCheck(DataFlow::Node cn, Expr e, boolean outcome) { cn.(DataFlow::CallNode) .getTarget() @@ -63,7 +75,9 @@ private predicate regexMatchCheck(DataFlow::Node cn, Expr e, boolean outcome) { */ class UntrustedUnicodeCharChecks extends DataFlow::Node { UntrustedUnicodeCharChecks() { - this = DataFlow::BarrierGuard ::getABarrierNode() + this = DataFlow::BarrierGuard ::getABarrierNode() + or + this = DataFlow::BarrierGuard ::getABarrierNode() or this = DataFlow::BarrierGuard ::getABarrierNode() or From e0761a75fa209a56eda13bf394831c177d2eb930 Mon Sep 17 00:00:00 2001 From: Sim4n6 Date: Mon, 26 Jun 2023 11:31:03 +0100 Subject: [PATCH 34/36] Fix the count check is for a count function call only. --- go/ql/lib/semmle/go/security/UnicodeBypassValidationQuery.qll | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/go/ql/lib/semmle/go/security/UnicodeBypassValidationQuery.qll b/go/ql/lib/semmle/go/security/UnicodeBypassValidationQuery.qll index da6721a50170..431cb66f0105 100644 --- a/go/ql/lib/semmle/go/security/UnicodeBypassValidationQuery.qll +++ b/go/ql/lib/semmle/go/security/UnicodeBypassValidationQuery.qll @@ -38,8 +38,7 @@ private predicate countCheck(DataFlow::Node g, Expr e, boolean outcome) { exists(DataFlow::CallNode cn, DataFlow::EqualityTestNode etn | g = etn and DataFlow::localFlow(cn.getResult(), etn.getAnOperand()) and - cn.getTarget() - .hasQualifiedName("strings", ["Count", "CountAny", "CountByte", "CountFunc", "CountRune"]) and + cn.getTarget().hasQualifiedName("strings", "Count") and cn.getArgument(0).asExpr() = e and etn.getAnOperand().getIntValue() = 0 and outcome = etn.getPolarity() From ab97d15061c783ab9653c7fb1b8acfff73bfb912 Mon Sep 17 00:00:00 2001 From: Sim4n6 Date: Wed, 28 Jun 2023 13:00:23 +0100 Subject: [PATCH 35/36] Added an example code to support strings.Count() --- .../CWE-176/UnicodeBypassValidation.go | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/go/ql/test/experimental/CWE-176/UnicodeBypassValidation.go b/go/ql/test/experimental/CWE-176/UnicodeBypassValidation.go index e90c0dd7707e..65ec1b34aef3 100644 --- a/go/ql/test/experimental/CWE-176/UnicodeBypassValidation.go +++ b/go/ql/test/experimental/CWE-176/UnicodeBypassValidation.go @@ -4,8 +4,8 @@ import ( "fmt" "html" "net/http" - "strings" "regexp" + "strings" "golang.org/x/text/unicode/norm" ) @@ -63,7 +63,7 @@ func bad() { http.HandleFunc("/bad_4", func(w http.ResponseWriter, req *http.Request) { - // BAD: Unicode normalization is performed after the regex match validation is performed + // BAD: Unicode normalization is performed after the regex match validation is performed // against the unsafe characters "<" and ">". This may be bypassed using the Unicode characters // equivalent to "<" and ">". unicode_input := req.URL.Query().Get("unicode_input") @@ -77,5 +77,18 @@ func bad() { }) + http.HandleFunc("/bad_5", func(w http.ResponseWriter, req *http.Request) { + + // BAD: Unicode normalization is performed after the check whether the unicode_input contains an unsafe characters + // "<" and ">". This may be bypassed using the Unicode characters equivalent to "<" and ">". + unicode_input := req.URL.Query().Get("unicode_input") + if strings.Count("<", unicode_input) > 0 || strings.Count(">", unicode_input) > 0 { + fmt.Println("The input is not safe.") + } else { + unicode_norm := norm.NFKC.String(unicode_input) // $result=BAD + fmt.Println(w, "Results: %q", unicode_norm) + } + + }) + } -u \ No newline at end of file From 1eaf71a6ac76fdda3ef48c3fbf2c31d2530f6118 Mon Sep 17 00:00:00 2001 From: Sim4n6 Date: Wed, 28 Jun 2023 13:00:47 +0100 Subject: [PATCH 36/36] Use of RelationalComparisonNode --- .../go/security/UnicodeBypassValidationQuery.qll | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/go/ql/lib/semmle/go/security/UnicodeBypassValidationQuery.qll b/go/ql/lib/semmle/go/security/UnicodeBypassValidationQuery.qll index 431cb66f0105..13622770455e 100644 --- a/go/ql/lib/semmle/go/security/UnicodeBypassValidationQuery.qll +++ b/go/ql/lib/semmle/go/security/UnicodeBypassValidationQuery.qll @@ -35,13 +35,16 @@ private predicate indexCheck(DataFlow::Node g, Expr e, boolean outcome) { } private predicate countCheck(DataFlow::Node g, Expr e, boolean outcome) { - exists(DataFlow::CallNode cn, DataFlow::EqualityTestNode etn | - g = etn and - DataFlow::localFlow(cn.getResult(), etn.getAnOperand()) and + exists( + DataFlow::RelationalComparisonNode cmp, DataFlow::CallNode cn, DataFlow::Node zero, + DataFlow::Node r + | + g = cmp and + DataFlow::localFlow(cn.getResult(), r) and cn.getTarget().hasQualifiedName("strings", "Count") and - cn.getArgument(0).asExpr() = e and - etn.getAnOperand().getIntValue() = 0 and - outcome = etn.getPolarity() + cn.getArgument(1).asExpr() = e and + zero.getNumericValue() = 0 and + cmp.leq(outcome, r, zero, 0) ) }