@@ -860,6 +860,167 @@ describe('(GHSA-mf3j-86qx-cq5j) ReDoS via $regex in LiveQuery subscription', ()
860860 } ) ;
861861} ) ;
862862
863+ describe ( '(GHSA-qpr4-jrj4-6f27) SQL Injection via sort dot-notation field name' , ( ) => {
864+ const headers = {
865+ 'Content-Type' : 'application/json' ,
866+ 'X-Parse-Application-Id' : 'test' ,
867+ 'X-Parse-REST-API-Key' : 'rest' ,
868+ } ;
869+
870+ it_only_db ( 'postgres' ) ( 'does not execute injected SQL via sort order dot-notation' , async ( ) => {
871+ const obj = new Parse . Object ( 'InjectionTest' ) ;
872+ obj . set ( 'data' , { key : 'value' } ) ;
873+ obj . set ( 'name' , 'original' ) ;
874+ await obj . save ( ) ;
875+
876+ // This payload would execute a stacked query if single quotes are not escaped
877+ await request ( {
878+ method : 'GET' ,
879+ url : 'http://localhost:8378/1/classes/InjectionTest' ,
880+ headers,
881+ qs : {
882+ order : "data.x' ASC; UPDATE \"InjectionTest\" SET name = 'hacked' WHERE true--" ,
883+ } ,
884+ } ) . catch ( ( ) => { } ) ;
885+
886+ // Verify the data was not modified by injected SQL
887+ const verify = await new Parse . Query ( 'InjectionTest' ) . get ( obj . id ) ;
888+ expect ( verify . get ( 'name' ) ) . toBe ( 'original' ) ;
889+ } ) ;
890+
891+ it_only_db ( 'postgres' ) ( 'does not execute injected SQL via sort order with pg_sleep' , async ( ) => {
892+ const obj = new Parse . Object ( 'InjectionTest' ) ;
893+ obj . set ( 'data' , { key : 'value' } ) ;
894+ await obj . save ( ) ;
895+
896+ const start = Date . now ( ) ;
897+ await request ( {
898+ method : 'GET' ,
899+ url : 'http://localhost:8378/1/classes/InjectionTest' ,
900+ headers,
901+ qs : {
902+ order : "data.x' ASC; SELECT pg_sleep(3)--" ,
903+ } ,
904+ } ) . catch ( ( ) => { } ) ;
905+ const elapsed = Date . now ( ) - start ;
906+
907+ // If injection succeeded, query would take >= 3 seconds
908+ expect ( elapsed ) . toBeLessThan ( 3000 ) ;
909+ } ) ;
910+
911+ it_only_db ( 'postgres' ) ( 'does not execute injection via dollar-sign quoting bypass' , async ( ) => {
912+ // PostgreSQL supports $$string$$ as alternative to 'string'
913+ const obj = new Parse . Object ( 'InjectionTest' ) ;
914+ obj . set ( 'data' , { key : 'value' } ) ;
915+ obj . set ( 'name' , 'original' ) ;
916+ await obj . save ( ) ;
917+
918+ await request ( {
919+ method : 'GET' ,
920+ url : 'http://localhost:8378/1/classes/InjectionTest' ,
921+ headers,
922+ qs : {
923+ order : "data.x' ASC; UPDATE \"InjectionTest\" SET name = $$hacked$$ WHERE true--" ,
924+ } ,
925+ } ) . catch ( ( ) => { } ) ;
926+
927+ const verify = await new Parse . Query ( 'InjectionTest' ) . get ( obj . id ) ;
928+ expect ( verify . get ( 'name' ) ) . toBe ( 'original' ) ;
929+ } ) ;
930+
931+ it_only_db ( 'postgres' ) ( 'does not execute injection via tagged dollar quoting bypass' , async ( ) => {
932+ // PostgreSQL supports $tag$string$tag$ as alternative to 'string'
933+ const obj = new Parse . Object ( 'InjectionTest' ) ;
934+ obj . set ( 'data' , { key : 'value' } ) ;
935+ obj . set ( 'name' , 'original' ) ;
936+ await obj . save ( ) ;
937+
938+ await request ( {
939+ method : 'GET' ,
940+ url : 'http://localhost:8378/1/classes/InjectionTest' ,
941+ headers,
942+ qs : {
943+ order : "data.x' ASC; UPDATE \"InjectionTest\" SET name = $t$hacked$t$ WHERE true--" ,
944+ } ,
945+ } ) . catch ( ( ) => { } ) ;
946+
947+ const verify = await new Parse . Query ( 'InjectionTest' ) . get ( obj . id ) ;
948+ expect ( verify . get ( 'name' ) ) . toBe ( 'original' ) ;
949+ } ) ;
950+
951+ it_only_db ( 'postgres' ) ( 'does not execute injection via CHR() concatenation bypass' , async ( ) => {
952+ // CHR(104)||CHR(97)||... builds 'hacked' without quotes
953+ const obj = new Parse . Object ( 'InjectionTest' ) ;
954+ obj . set ( 'data' , { key : 'value' } ) ;
955+ obj . set ( 'name' , 'original' ) ;
956+ await obj . save ( ) ;
957+
958+ await request ( {
959+ method : 'GET' ,
960+ url : 'http://localhost:8378/1/classes/InjectionTest' ,
961+ headers,
962+ qs : {
963+ order : "data.x' ASC; UPDATE \"InjectionTest\" SET name = CHR(104)||CHR(97)||CHR(99)||CHR(107) WHERE true--" ,
964+ } ,
965+ } ) . catch ( ( ) => { } ) ;
966+
967+ const verify = await new Parse . Query ( 'InjectionTest' ) . get ( obj . id ) ;
968+ expect ( verify . get ( 'name' ) ) . toBe ( 'original' ) ;
969+ } ) ;
970+
971+ it_only_db ( 'postgres' ) ( 'does not execute injection via backslash escape bypass' , async ( ) => {
972+ // Backslash before quote could interact with '' escaping in some configurations
973+ const obj = new Parse . Object ( 'InjectionTest' ) ;
974+ obj . set ( 'data' , { key : 'value' } ) ;
975+ obj . set ( 'name' , 'original' ) ;
976+ await obj . save ( ) ;
977+
978+ await request ( {
979+ method : 'GET' ,
980+ url : 'http://localhost:8378/1/classes/InjectionTest' ,
981+ headers,
982+ qs : {
983+ order : "data.x\\' ASC; UPDATE \"InjectionTest\" SET name = 'hacked' WHERE true--" ,
984+ } ,
985+ } ) . catch ( ( ) => { } ) ;
986+
987+ const verify = await new Parse . Query ( 'InjectionTest' ) . get ( obj . id ) ;
988+ expect ( verify . get ( 'name' ) ) . toBe ( 'original' ) ;
989+ } ) ;
990+
991+ it ( 'allows valid dot-notation sort on object field' , async ( ) => {
992+ const obj = new Parse . Object ( 'InjectionTest' ) ;
993+ obj . set ( 'data' , { key : 'value' } ) ;
994+ await obj . save ( ) ;
995+
996+ const response = await request ( {
997+ method : 'GET' ,
998+ url : 'http://localhost:8378/1/classes/InjectionTest' ,
999+ headers,
1000+ qs : {
1001+ order : 'data.key' ,
1002+ } ,
1003+ } ) ;
1004+ expect ( response . status ) . toBe ( 200 ) ;
1005+ } ) ;
1006+
1007+ it ( 'allows valid dot-notation with special characters in sub-field' , async ( ) => {
1008+ const obj = new Parse . Object ( 'InjectionTest' ) ;
1009+ obj . set ( 'data' , { 'my-field' : 'value' } ) ;
1010+ await obj . save ( ) ;
1011+
1012+ const response = await request ( {
1013+ method : 'GET' ,
1014+ url : 'http://localhost:8378/1/classes/InjectionTest' ,
1015+ headers,
1016+ qs : {
1017+ order : 'data.my-field' ,
1018+ } ,
1019+ } ) ;
1020+ expect ( response . status ) . toBe ( 200 ) ;
1021+ } ) ;
1022+ } ) ;
1023+
8631024describe ( '(GHSA-3jmq-rrxf-gqrg) Stored XSS via file serving' , ( ) => {
8641025 it ( 'sets X-Content-Type-Options: nosniff on file GET response' , async ( ) => {
8651026 const file = new Parse . File ( 'hello.txt' , [ 1 , 2 , 3 ] , 'text/plain' ) ;
0 commit comments