@@ -918,6 +918,126 @@ describe('Vulnerabilities', () => {
918918 await expectAsync ( obj . save ( ) ) . toBeResolved ( ) ;
919919 } ) ;
920920 } ) ;
921+
922+ describe ( '(GHSA-mmg8-87c5-jrc2) LiveQuery protected-field guard bypass via array-like $or/$and/$nor' , ( ) => {
923+ const { sleep } = require ( '../lib/TestUtils' ) ;
924+ let obj ;
925+
926+ beforeEach ( async ( ) => {
927+ Parse . CoreManager . getLiveQueryController ( ) . setDefaultLiveQueryClient ( null ) ;
928+ await reconfigureServer ( {
929+ liveQuery : { classNames : [ 'SecretClass' ] } ,
930+ startLiveQueryServer : true ,
931+ verbose : false ,
932+ silent : true ,
933+ } ) ;
934+ const config = Config . get ( Parse . applicationId ) ;
935+ const schemaController = await config . database . loadSchema ( ) ;
936+ await schemaController . addClassIfNotExists (
937+ 'SecretClass' ,
938+ { secretObj : { type : 'Object' } , publicField : { type : 'String' } } ,
939+ ) ;
940+ await schemaController . updateClass (
941+ 'SecretClass' ,
942+ { } ,
943+ {
944+ find : { '*' : true } ,
945+ get : { '*' : true } ,
946+ create : { '*' : true } ,
947+ update : { '*' : true } ,
948+ delete : { '*' : true } ,
949+ addField : { } ,
950+ protectedFields : { '*' : [ 'secretObj' ] } ,
951+ }
952+ ) ;
953+
954+ obj = new Parse . Object ( 'SecretClass' ) ;
955+ obj . set ( 'secretObj' , { apiKey : 'SENSITIVE_KEY_123' , score : 42 } ) ;
956+ obj . set ( 'publicField' , 'visible' ) ;
957+ await obj . save ( null , { useMasterKey : true } ) ;
958+ } ) ;
959+
960+ afterEach ( async ( ) => {
961+ const client = await Parse . CoreManager . getLiveQueryController ( ) . getDefaultLiveQueryClient ( ) ;
962+ if ( client ) {
963+ await client . close ( ) ;
964+ }
965+ } ) ;
966+
967+ it ( 'should reject subscription with array-like $or containing protected field' , async ( ) => {
968+ const query = new Parse . Query ( 'SecretClass' ) ;
969+ query . _where = {
970+ $or : { '0' : { 'secretObj.apiKey' : 'SENSITIVE_KEY_123' } , length : 1 } ,
971+ } ;
972+ await expectAsync ( query . subscribe ( ) ) . toBeRejectedWith (
973+ jasmine . objectContaining ( { code : Parse . Error . INVALID_QUERY } )
974+ ) ;
975+ } ) ;
976+
977+ it ( 'should reject subscription with array-like $and containing protected field' , async ( ) => {
978+ const query = new Parse . Query ( 'SecretClass' ) ;
979+ query . _where = {
980+ $and : { '0' : { 'secretObj.apiKey' : 'SENSITIVE_KEY_123' } , '1' : { publicField : 'visible' } , length : 2 } ,
981+ } ;
982+ await expectAsync ( query . subscribe ( ) ) . toBeRejectedWith (
983+ jasmine . objectContaining ( { code : Parse . Error . INVALID_QUERY } )
984+ ) ;
985+ } ) ;
986+
987+ it ( 'should reject subscription with array-like $nor containing protected field' , async ( ) => {
988+ const query = new Parse . Query ( 'SecretClass' ) ;
989+ query . _where = {
990+ $nor : { '0' : { 'secretObj.apiKey' : 'SENSITIVE_KEY_123' } , length : 1 } ,
991+ } ;
992+ await expectAsync ( query . subscribe ( ) ) . toBeRejectedWith (
993+ jasmine . objectContaining ( { code : Parse . Error . INVALID_QUERY } )
994+ ) ;
995+ } ) ;
996+
997+ it ( 'should reject subscription with array-like $or even on non-protected fields' , async ( ) => {
998+ const query = new Parse . Query ( 'SecretClass' ) ;
999+ query . _where = {
1000+ $or : { '0' : { publicField : 'visible' } , length : 1 } ,
1001+ } ;
1002+ await expectAsync ( query . subscribe ( ) ) . toBeRejectedWith (
1003+ jasmine . objectContaining ( { code : Parse . Error . INVALID_QUERY } )
1004+ ) ;
1005+ } ) ;
1006+
1007+ it ( 'should not create oracle via array-like $or bypass on protected fields' , async ( ) => {
1008+ const query = new Parse . Query ( 'SecretClass' ) ;
1009+ query . _where = {
1010+ $or : { '0' : { 'secretObj.apiKey' : 'SENSITIVE_KEY_123' } , length : 1 } ,
1011+ } ;
1012+
1013+ // Subscription must be rejected; no event oracle should be possible
1014+ let subscriptionError ;
1015+ let subscription ;
1016+ try {
1017+ subscription = await query . subscribe ( ) ;
1018+ } catch ( e ) {
1019+ subscriptionError = e ;
1020+ }
1021+
1022+ if ( ! subscriptionError ) {
1023+ const updateSpy = jasmine . createSpy ( 'update' ) ;
1024+ subscription . on ( 'create' , updateSpy ) ;
1025+ subscription . on ( 'update' , updateSpy ) ;
1026+
1027+ // Trigger an object change
1028+ obj . set ( 'publicField' , 'changed' ) ;
1029+ await obj . save ( null , { useMasterKey : true } ) ;
1030+ await sleep ( 500 ) ;
1031+
1032+ // If subscription somehow accepted, verify no events fired (evaluator defense)
1033+ expect ( updateSpy ) . not . toHaveBeenCalled ( ) ;
1034+ fail ( 'Expected subscription to be rejected' ) ;
1035+ }
1036+ expect ( subscriptionError ) . toEqual (
1037+ jasmine . objectContaining ( { code : Parse . Error . INVALID_QUERY } )
1038+ ) ;
1039+ } ) ;
1040+ } ) ;
9211041} ) ;
9221042
9231043describe ( 'Malformed $regex information disclosure' , ( ) => {
@@ -5286,123 +5406,3 @@ describe('(GHSA-p2w6-rmh7-w8q3) SQL Injection via aggregate and distinct field n
52865406 } ) ;
52875407 } ) ;
52885408} ) ;
5289-
5290- describe ( '(GHSA-mmg8-87c5-jrc2) LiveQuery protected-field guard bypass via array-like $or/$and/$nor' , ( ) => {
5291- const { sleep } = require ( '../lib/TestUtils' ) ;
5292- let obj ;
5293-
5294- beforeEach ( async ( ) => {
5295- Parse . CoreManager . getLiveQueryController ( ) . setDefaultLiveQueryClient ( null ) ;
5296- await reconfigureServer ( {
5297- liveQuery : { classNames : [ 'SecretClass' ] } ,
5298- startLiveQueryServer : true ,
5299- verbose : false ,
5300- silent : true ,
5301- } ) ;
5302- const config = Config . get ( Parse . applicationId ) ;
5303- const schemaController = await config . database . loadSchema ( ) ;
5304- await schemaController . addClassIfNotExists (
5305- 'SecretClass' ,
5306- { secretObj : { type : 'Object' } , publicField : { type : 'String' } } ,
5307- ) ;
5308- await schemaController . updateClass (
5309- 'SecretClass' ,
5310- { } ,
5311- {
5312- find : { '*' : true } ,
5313- get : { '*' : true } ,
5314- create : { '*' : true } ,
5315- update : { '*' : true } ,
5316- delete : { '*' : true } ,
5317- addField : { } ,
5318- protectedFields : { '*' : [ 'secretObj' ] } ,
5319- }
5320- ) ;
5321-
5322- obj = new Parse . Object ( 'SecretClass' ) ;
5323- obj . set ( 'secretObj' , { apiKey : 'SENSITIVE_KEY_123' , score : 42 } ) ;
5324- obj . set ( 'publicField' , 'visible' ) ;
5325- await obj . save ( null , { useMasterKey : true } ) ;
5326- } ) ;
5327-
5328- afterEach ( async ( ) => {
5329- const client = await Parse . CoreManager . getLiveQueryController ( ) . getDefaultLiveQueryClient ( ) ;
5330- if ( client ) {
5331- await client . close ( ) ;
5332- }
5333- } ) ;
5334-
5335- it ( 'should reject subscription with array-like $or containing protected field' , async ( ) => {
5336- const query = new Parse . Query ( 'SecretClass' ) ;
5337- query . _where = {
5338- $or : { '0' : { 'secretObj.apiKey' : 'SENSITIVE_KEY_123' } , length : 1 } ,
5339- } ;
5340- await expectAsync ( query . subscribe ( ) ) . toBeRejectedWith (
5341- jasmine . objectContaining ( { code : Parse . Error . INVALID_QUERY } )
5342- ) ;
5343- } ) ;
5344-
5345- it ( 'should reject subscription with array-like $and containing protected field' , async ( ) => {
5346- const query = new Parse . Query ( 'SecretClass' ) ;
5347- query . _where = {
5348- $and : { '0' : { 'secretObj.apiKey' : 'SENSITIVE_KEY_123' } , '1' : { publicField : 'visible' } , length : 2 } ,
5349- } ;
5350- await expectAsync ( query . subscribe ( ) ) . toBeRejectedWith (
5351- jasmine . objectContaining ( { code : Parse . Error . INVALID_QUERY } )
5352- ) ;
5353- } ) ;
5354-
5355- it ( 'should reject subscription with array-like $nor containing protected field' , async ( ) => {
5356- const query = new Parse . Query ( 'SecretClass' ) ;
5357- query . _where = {
5358- $nor : { '0' : { 'secretObj.apiKey' : 'SENSITIVE_KEY_123' } , length : 1 } ,
5359- } ;
5360- await expectAsync ( query . subscribe ( ) ) . toBeRejectedWith (
5361- jasmine . objectContaining ( { code : Parse . Error . INVALID_QUERY } )
5362- ) ;
5363- } ) ;
5364-
5365- it ( 'should reject subscription with array-like $or even on non-protected fields' , async ( ) => {
5366- const query = new Parse . Query ( 'SecretClass' ) ;
5367- query . _where = {
5368- $or : { '0' : { publicField : 'visible' } , length : 1 } ,
5369- } ;
5370- await expectAsync ( query . subscribe ( ) ) . toBeRejectedWith (
5371- jasmine . objectContaining ( { code : Parse . Error . INVALID_QUERY } )
5372- ) ;
5373- } ) ;
5374-
5375- it ( 'should not create oracle via array-like $or bypass on protected fields' , async ( ) => {
5376- const query = new Parse . Query ( 'SecretClass' ) ;
5377- query . _where = {
5378- $or : { '0' : { 'secretObj.apiKey' : 'SENSITIVE_KEY_123' } , length : 1 } ,
5379- } ;
5380-
5381- // Subscription must be rejected; no event oracle should be possible
5382- let subscriptionError ;
5383- let subscription ;
5384- try {
5385- subscription = await query . subscribe ( ) ;
5386- } catch ( e ) {
5387- subscriptionError = e ;
5388- }
5389-
5390- if ( ! subscriptionError ) {
5391- const updateSpy = jasmine . createSpy ( 'update' ) ;
5392- subscription . on ( 'create' , updateSpy ) ;
5393- subscription . on ( 'update' , updateSpy ) ;
5394-
5395- // Trigger an object change
5396- obj . set ( 'publicField' , 'changed' ) ;
5397- await obj . save ( null , { useMasterKey : true } ) ;
5398- await sleep ( 500 ) ;
5399-
5400- // If subscription somehow accepted, verify no events fired (evaluator defense)
5401- expect ( updateSpy ) . not . toHaveBeenCalled ( ) ;
5402- fail ( 'Expected subscription to be rejected' ) ;
5403- }
5404- expect ( subscriptionError ) . toEqual (
5405- jasmine . objectContaining ( { code : Parse . Error . INVALID_QUERY } )
5406- ) ;
5407- } ) ;
5408- } ) ;
0 commit comments