@@ -4206,3 +4206,187 @@ describe('(GHSA-g4cf-xj29-wqqr) DoS via unindexed database query for unconfigure
42064206 expect ( authDataQueries . length ) . toBeGreaterThan ( 0 ) ;
42074207 } ) ;
42084208} ) ;
4209+
4210+ describe ( '(GHSA-p2w6-rmh7-w8q3) SQL Injection via aggregate and distinct field names in PostgreSQL adapter' , ( ) => {
4211+ const headers = {
4212+ 'Content-Type' : 'application/json' ,
4213+ 'X-Parse-Application-Id' : 'test' ,
4214+ 'X-Parse-REST-API-Key' : 'rest' ,
4215+ 'X-Parse-Master-Key' : 'test' ,
4216+ } ;
4217+ const serverURL = 'http://localhost:8378/1' ;
4218+
4219+ beforeEach ( async ( ) => {
4220+ const obj = new Parse . Object ( 'TestClass' ) ;
4221+ obj . set ( 'playerName' , 'Alice' ) ;
4222+ obj . set ( 'score' , 100 ) ;
4223+ obj . set ( 'metadata' , { tag : 'hello' } ) ;
4224+ await obj . save ( null , { useMasterKey : true } ) ;
4225+ } ) ;
4226+
4227+ describe ( 'aggregate $group._id SQL injection' , ( ) => {
4228+ it_only_db ( 'postgres' ) ( 'rejects $group._id field value containing double quotes' , async ( ) => {
4229+ const response = await request ( {
4230+ method : 'GET' ,
4231+ url : `${ serverURL } /aggregate/TestClass` ,
4232+ headers,
4233+ qs : {
4234+ pipeline : JSON . stringify ( [
4235+ {
4236+ $group : {
4237+ _id : {
4238+ alias : '$playerName" OR 1=1 --' ,
4239+ } ,
4240+ } ,
4241+ } ,
4242+ ] ) ,
4243+ } ,
4244+ } ) . catch ( e => e ) ;
4245+ expect ( response . data ?. code ) . toBe ( Parse . Error . INVALID_KEY_NAME ) ;
4246+ } ) ;
4247+
4248+ it_only_db ( 'postgres' ) ( 'rejects $group._id field value containing semicolons' , async ( ) => {
4249+ const response = await request ( {
4250+ method : 'GET' ,
4251+ url : `${ serverURL } /aggregate/TestClass` ,
4252+ headers,
4253+ qs : {
4254+ pipeline : JSON . stringify ( [
4255+ {
4256+ $group : {
4257+ _id : {
4258+ alias : '$playerName"; DROP TABLE "TestClass" --' ,
4259+ } ,
4260+ } ,
4261+ } ,
4262+ ] ) ,
4263+ } ,
4264+ } ) . catch ( e => e ) ;
4265+ expect ( response . data ?. code ) . toBe ( Parse . Error . INVALID_KEY_NAME ) ;
4266+ } ) ;
4267+
4268+ it_only_db ( 'postgres' ) ( 'rejects $group._id date operation field value containing double quotes' , async ( ) => {
4269+ const response = await request ( {
4270+ method : 'GET' ,
4271+ url : `${ serverURL } /aggregate/TestClass` ,
4272+ headers,
4273+ qs : {
4274+ pipeline : JSON . stringify ( [
4275+ {
4276+ $group : {
4277+ _id : {
4278+ day : { $dayOfMonth : '$createdAt" OR 1=1 --' } ,
4279+ } ,
4280+ } ,
4281+ } ,
4282+ ] ) ,
4283+ } ,
4284+ } ) . catch ( e => e ) ;
4285+ expect ( response . data ?. code ) . toBe ( Parse . Error . INVALID_KEY_NAME ) ;
4286+ } ) ;
4287+
4288+ it_only_db ( 'postgres' ) ( 'allows legitimate $group._id with field reference' , async ( ) => {
4289+ const response = await request ( {
4290+ method : 'GET' ,
4291+ url : `${ serverURL } /aggregate/TestClass` ,
4292+ headers,
4293+ qs : {
4294+ pipeline : JSON . stringify ( [
4295+ {
4296+ $group : {
4297+ _id : {
4298+ name : '$playerName' ,
4299+ } ,
4300+ count : { $sum : 1 } ,
4301+ } ,
4302+ } ,
4303+ ] ) ,
4304+ } ,
4305+ } ) ;
4306+ expect ( response . data ?. results ?. length ) . toBeGreaterThan ( 0 ) ;
4307+ } ) ;
4308+
4309+ it_only_db ( 'postgres' ) ( 'allows legitimate $group._id with date extraction' , async ( ) => {
4310+ const response = await request ( {
4311+ method : 'GET' ,
4312+ url : `${ serverURL } /aggregate/TestClass` ,
4313+ headers,
4314+ qs : {
4315+ pipeline : JSON . stringify ( [
4316+ {
4317+ $group : {
4318+ _id : {
4319+ day : { $dayOfMonth : '$_created_at' } ,
4320+ } ,
4321+ count : { $sum : 1 } ,
4322+ } ,
4323+ } ,
4324+ ] ) ,
4325+ } ,
4326+ } ) ;
4327+ expect ( response . data ?. results ?. length ) . toBeGreaterThan ( 0 ) ;
4328+ } ) ;
4329+ } ) ;
4330+
4331+ describe ( 'distinct dot-notation SQL injection' , ( ) => {
4332+ it_only_db ( 'postgres' ) ( 'rejects distinct field name containing double quotes in dot notation' , async ( ) => {
4333+ const response = await request ( {
4334+ method : 'GET' ,
4335+ url : `${ serverURL } /aggregate/TestClass` ,
4336+ headers,
4337+ qs : {
4338+ distinct : 'metadata" FROM pg_tables; --.tag' ,
4339+ } ,
4340+ } ) . catch ( e => e ) ;
4341+ expect ( response . data ?. code ) . toBe ( Parse . Error . INVALID_KEY_NAME ) ;
4342+ } ) ;
4343+
4344+ it_only_db ( 'postgres' ) ( 'rejects distinct field name containing semicolons in dot notation' , async ( ) => {
4345+ const response = await request ( {
4346+ method : 'GET' ,
4347+ url : `${ serverURL } /aggregate/TestClass` ,
4348+ headers,
4349+ qs : {
4350+ distinct : 'metadata; DROP TABLE "TestClass" --.tag' ,
4351+ } ,
4352+ } ) . catch ( e => e ) ;
4353+ expect ( response . data ?. code ) . toBe ( Parse . Error . INVALID_KEY_NAME ) ;
4354+ } ) ;
4355+
4356+ it_only_db ( 'postgres' ) ( 'rejects distinct field name containing single quotes in dot notation' , async ( ) => {
4357+ const response = await request ( {
4358+ method : 'GET' ,
4359+ url : `${ serverURL } /aggregate/TestClass` ,
4360+ headers,
4361+ qs : {
4362+ distinct : "metadata' OR '1'='1.tag" ,
4363+ } ,
4364+ } ) . catch ( e => e ) ;
4365+ expect ( response . data ?. code ) . toBe ( Parse . Error . INVALID_KEY_NAME ) ;
4366+ } ) ;
4367+
4368+ it_only_db ( 'postgres' ) ( 'allows legitimate distinct with dot notation' , async ( ) => {
4369+ const response = await request ( {
4370+ method : 'GET' ,
4371+ url : `${ serverURL } /aggregate/TestClass` ,
4372+ headers,
4373+ qs : {
4374+ distinct : 'metadata.tag' ,
4375+ } ,
4376+ } ) ;
4377+ expect ( response . data ?. results ) . toEqual ( [ 'hello' ] ) ;
4378+ } ) ;
4379+
4380+ it_only_db ( 'postgres' ) ( 'allows legitimate distinct without dot notation' , async ( ) => {
4381+ const response = await request ( {
4382+ method : 'GET' ,
4383+ url : `${ serverURL } /aggregate/TestClass` ,
4384+ headers,
4385+ qs : {
4386+ distinct : 'playerName' ,
4387+ } ,
4388+ } ) ;
4389+ expect ( response . data ?. results ) . toEqual ( [ 'Alice' ] ) ;
4390+ } ) ;
4391+ } ) ;
4392+ } ) ;
0 commit comments