Kotlin/Spring: Arbitrary Code Execution: Is this endpoint vulnerable to code injection? - spring

Assume the following API endpoint controller
enum class AccessMethod {
SSO,
BASIC;
}
internal open class SomeController {
[...]
#PostMapping("{accessMethod}")
open fun trigger(#PathVariable("accessMethod", required = true) accessMethod: AccessMethod) {
logger.info {"Is arbitrary code execution possible here in spring? $accessMethod"}
}
}
I am not well aware of the validation mechanisms of Spring. Does the input get sanitised by default in the enum case a.k.a will Spring throw an error?
Malicious PoC
val malicious_payload = "\"} malicious() logger.info {\"Code Injection Successful"
client.post(endpoint_of_the_function_above + malicious_payload )

My first thought is that if the input value does not an ENUM value (string representation), it generates an error. So Enum enabled "SSO" and "BASIC" and throws everything else back.
Kotlin (like Java) is not a script language, so you can not use kotlin or java language directly for inject, because it have to build to bytecode and run on JVM. Thus, the operation cannot be manipulated.
Ofc, you can manipulate generate any query language (SQL, NoSQL or send message, etc) by which only travel through the system. Of course, the frameworks mainly handle it, but errors can always occur, right?
(Log4Shell was created because Log4J processing enabled external sources, so here it wasn't a case of code manipulated either.)
Example: improperly handled identifiers with which SQL queries are created.
I created a small project and used your "vuln" string. Just it use URL so this string converted to URL encoded.
It will say HTTP400 for any URL that is not /SSO and /BASIC. By definition, an /asdasd will also have an HTTP400 result.
\"} malicious() logger.info {\"Code Injection Successful to %5C%22%7D%20malicious%28%29%20logger.info%20%7B%5C%22Code%20Injection%20Successful
Request:
POST http://localhost:8080/%5C%22%7D%20malicious%28%29%20logger.info%20%7B%5C%22Code%20Injection%20Successful
Response:
{
"timestamp": "2022-08-25T12:51:23.092+00:00",
"path": "/%5C%22%7D%20malicious%28%29%20logger.info%20%7B%5C%22Code%20Injection%20Successful",
"status": 400,
"error": "Bad Request",
"requestId": "c3bf4acf-1"
}
Source (I mainly use the reactive environment, but this is not relevant now.)
package com.example.demo
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RestController
import reactor.core.publisher.Mono
enum class AccessMethod {
SSO,
BASIC;
}
#SpringBootApplication
#RestController
class DemoApplication {
val logger: Logger = LoggerFactory.getLogger(this::class.java)
#PostMapping("{accessMethod}")
fun trigger(#PathVariable("accessMethod", required = true) accessMethod: AccessMethod): Mono<Void> {
logger.info("Is arbitrary code execution possible here in spring? $accessMethod")
return Mono.empty()
}
}
fun main(args: Array<String>) {
runApplication<DemoApplication>(*args)
}

Related

Kotlin spring boot #RequestBody validation is not triggered

I have a problem on a project with validating #RequestBody by using
implementation("org.springframework.boot:spring-boot-starter-validation")
My DTO looks like this:
import javax.validation.constraints.Email
import javax.validation.constraints.Pattern
class LoginDto(
#Email
val email: String,
#Pattern(regexp = Constants.PASSWORD_REGEX)
val password: String
)
And Controller looks like this:
import org.springframework.validation.annotation.Validated
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RestController
import javax.validation.Valid
#RestController
#Validated
class AuthController(private val authService: AuthService) {
#PostMapping("login")
fun login(#Valid #RequestBody loginDto: LoginDto): LoginResponse {
return authService.login(loginDto)
}
...
}
And there is no error from validation, if I try to pass invalid data:
{
"password":"hello",
"email":"dfdfdfdf"
}
I get no error
I use Exposed instead of jpa but I think it's not related to the problem
You should change the annotations of #email and #Pattern to #field:Email and #field:Pattern for example.
The reason for this is twofold, on the one hand you place the annotations on Kotlin properties, and Kotlin properties kan be accessed in a variety of ways. Therefore, you need to specify how you want to access the property to apply the annotation on. On the other hand, the annotations have a set of predefined targets. You can inspect the annotation to see for example that it has a target of field. That's why we can use the #field:Pattern and #field:Email.
This is a key difference with java, where you have have distinct getters, setters, and fields amongst others.

spring-data-neo4j v6: No converter found capable of converting from type [MyDTO] to type [org.neo4j.driver.Value]

Situation
I'm migrating a kotlin spring data neo4j application from spring-data-neo4j version 5.2.0.RELEASE to version 6.0.11.
The original application has several Repository interfaces with custom queries which take some DTO as a parameter, and use the various DTO fields to construct the query. All those types of queries currently fail with
org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [MyDTO] to type [org.neo4j.driver.Value]
The reference documentation for spring-data-neo4j v6 only provides examples where parameters passed to custom query methods of a #Repository interface are of the same type as the #Node class associated with that repository. The documentation does not explicitly state that only parameters of the Node class are allowed.
Question
Is there any way to pass an arbitrary DTO (not being a #Node class) to a custom query method in a #Repository interface in spring-data-neo4j v6 like it was possible in v5?
Code samples
Example node entity
#Node
data class MyEntity(
#Id
val attr1: String,
val attr2: String,
val attr3: String
)
Example DTO
data class MyDTO(
val field1: String,
val field2: String
)
Example Repository interface
#Repository
interface MyRepository : PagingAndSortingRepository<MyEntity, String> {
// ConverterNotFoundException is thrown when this method is called
#Query("MATCH (e:MyEntity {attr1: {0}.field1}) " +
"CREATE (e)-[l:LINK]->(n:OtherEntity {attr2: {0}.field2))")
fun doSomethingWithDto(dto: MyDTO)
}
Solutions tried so far
Annotate DTO as if it were a Node entity
Based on the following found in the reference docs https://docs.spring.io/spring-data/neo4j/docs/current/reference/html/#custom-queries.parameters
Mapped entities (everything with a #Node) passed as parameter to a
function that is annotated with a custom query will be turned into a
nested map.
#Node
data class MyDTO(
#Id
val field1: String,
val field2: String
)
Replace {0} with $0 in custom query
Based on the following found in the reference docs https://docs.spring.io/spring-data/neo4j/docs/current/reference/html/#custom-queries.parameters
You do this exactly the same way as in a standard Cypher query issued
in the Neo4j Browser or the Cypher-Shell, with the $ syntax (from
Neo4j 4.0 on upwards, the old {foo} syntax for Cypher parameters has
been removed from the database).
...
[In the given listing] we are referring to the parameter by its name.
You can also use $0 etc. instead.
#Repository
interface MyRepository : PagingAndSortingRepository<MyEntity, String> {
// ConverterNotFoundException is thrown when this method is called
#Query("MATCH (e:MyEntity {attr1: $0.field1}) " +
"CREATE (e)-[l:LINK]->(n:OtherEntity {attr2: $0.field2))")
fun doSomethingWithDto(dto: MyDTO)
}
Details
spring-boot-starter: v2.4.10
spring-data-neo4j: v6.0.12
neo4j-java-driver: v4.1.4
Neo4j server version: v3.5.29
RTFM Custom conversions ...
Found the solution myself. Hopefully someone else may benefit from this as well.
Solution
Create a custom converter
import mypackage.model.*
import com.fasterxml.jackson.core.type.TypeReference
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import org.neo4j.driver.Value
import org.neo4j.driver.Values
import org.springframework.core.convert.TypeDescriptor
import org.springframework.core.convert.converter.GenericConverter
import org.springframework.core.convert.converter.GenericConverter.ConvertiblePair
import java.util.HashSet
class DtoToNeo4jValueConverter : GenericConverter {
override fun getConvertibleTypes(): Set<ConvertiblePair>? {
val convertiblePairs: MutableSet<ConvertiblePair> = HashSet()
convertiblePairs.add(ConvertiblePair(MyDTO::class.java, Value::class.java))
return convertiblePairs
}
override fun convert(source: Any?, sourceType: TypeDescriptor, targetType: TypeDescriptor?): Any? {
return if (MyDTO::class.java.isAssignableFrom(sourceType.type)) {
// generic way of converting an object into a map
val dataclassAsMap = jacksonObjectMapper().convertValue(source as MyDTO, object :
TypeReference<Map<String, Any>>() {})
Values.value(dataclassAsMap)
} else null
}
}
Register custom converter in config
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.data.neo4j.core.convert.Neo4jConversions
import org.springframework.core.convert.converter.GenericConverter
import java.util.*
#Configuration
class MyNeo4jConfig {
#Bean
override fun neo4jConversions(): Neo4jConversions? {
val additionalConverters: Set<GenericConverter?> = Collections.singleton(DtoToNeo4jValueConverter())
return Neo4jConversions(additionalConverters)
}
}
It's ridiculous that the framework would force you to write a custom converter for this. I made a #Transient object in my overridden User class for a limited set of update-able user profile fields, and I'm encountering the same error. I guess I will just have to break up the object into its component String fields in the method params to get around this problem. What a mess.
#Query("MATCH (u:User) WHERE u.username = :#{#username} SET u.firstName = :#{#up.firstName},u.lastName = :#{#up.firstName},u.intro = :#{#up.intro} RETURN u")
Mono<User> update(#Param("username") String username,#Param("up") UserProfile up);
No converter found capable of converting from type [...UserProfile] to type [org.neo4j.driver.Value]

Spring Validation of JSON - Why do I need to add `#field`

I've finally made some progress on Spring validation (on a JSON object coming in from RabbitMQ).
However there are a couple of things I don't understand:
In the documentation, it states I can just use the annotation #NotBlank then in my method I use the annotation #Valid. However I find this wasn't doing anything. So instead I did #field:NotBlank and it worked together with the following - why did this #field do the trick?
#JsonIgnoreProperties(ignoreUnknown = true)
data class MyModel (
#field:NotBlank(message = "ID cannot be blank")
val id : String = "",
#field:NotBlank(message = "s3FilePath cannot be blank")
val s3FilePath : String = ""
)
Then the function using this model:
#Service
class Listener {
#RabbitListener(queues = ["\${newsong.queue}"])
fun received(data: MyModel) {
val factory = Validation.buildDefaultValidatorFactory()
val validator = factory.validator
val validate = validator.validate(data)
// Then this `validate` will return an array of validation errors
println(validate)
}
}
Correct me if I'm wrong however I assumed just using #Valid and this point fun received(#Valid data: MyModel) it would just throw some exception for me to catch - any idea based on my code why this could have been?
Any advice/help would be appreciated.
Thanks.
Here are the imports:
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service
import javax.validation.*
import org.springframework.amqp.rabbit.core.RabbitTemplate
import org.springframework.amqp.rabbit.annotation.RabbitListener
import javax.validation.constraints.NotBlank
Quoting Kotlin's documentation for annotations:
When you're annotating a property or a primary constructor parameter, there are multiple Java elements which are generated from the corresponding Kotlin element, and therefore multiple possible locations for the annotation in the generated Java bytecode. To specify how exactly the annotation should be generated, use the following syntax:
class Example(#field:Ann val foo, // annotate Java field
#get:Ann val bar, // annotate Java getter
#param:Ann val quux) // annotate Java constructor parameter
So, until explicitly mention what you are annotating (field, getter or something else) in Kotlin class constructor, it won't automatically know where you want to put that annotation.

Test service from with dependencies in spock

I am working with a kotlin and spring project, Now I am trying to do the test of some service, which has some dependencies, I am having some problems, in order to get a success test. Maybe I my design is not good enough, moreover I have problems trying to call the method from the spy object, I am getting the issue: Cannot invoke real method 'getClubhouseFor' on interface based mock object. This is my code, Could you give me any idea about what I am doing bad.
Thanks in advance!!!!
This is my code:
import com.espn.csemobile.espnapp.models.UID
import com.espn.csemobile.espnapp.models.clubhouse.*
import com.espn.csemobile.espnapp.services.clubhouse.AutomatedClubhouseService
import com.espn.csemobile.espnapp.services.clubhouse.ClubhouseService
import com.espn.csemobile.espnapp.services.clubhouse.StaticClubhouseService
import com.espn.csemobile.espnapp.services.clubhouse.contexts.ClubhouseContext
import com.espn.csemobile.espnapp.services.core.CoreService
import rx.Single
import spock.lang.Specification
class ClubhouseServiceImplTest extends Specification {
StaticClubhouseService staticClubhouseService = GroovyStub()
AutomatedClubhouseService automatedClubhouseService = GroovyStub()
CoreService coreService = GroovyStub()
ClubhouseContext clubhouseContext = GroovyMock()
Clubhouse clubHouse
ClubhouseLogo clubhouseLogo
ClubhouseService spy = GroovySpy(ClubhouseService)
void setup() {
clubhouseLogo = new ClubhouseLogo("http://www.google.com", true)
clubHouse = new Clubhouse(new UID(), "summaryType", ClubhouseType.League, new ClubhouseLayout(), "summaryName", "MLB", clubhouseLogo, "http://www.google.com", "liveSportProp",new ArrayList<Integer>(), new ArrayList<ClubhouseSection>(),new ArrayList<ClubhouseAction>(), new HashMap<String, String>())
}
def "GetClubhouseFor"() {
given:
staticClubhouseService.getClubhouseFor(clubhouseContext) >> buildClubHouseMockService()
// The idea here is to get different responses it depends on the class of call.
automatedClubhouseService.getClubhouseFor(clubhouseContext ) >> buildClubHouseMockService()
spy.getClubhouseFor(clubhouseContext) >> spy.getClubhouseFor(clubhouseContext)
when:
def actual = spy.getClubhouseFor(clubhouseContext)
then:
actual != null
}
def buildClubHouseMockService(){
return Single.just(clubHouse)
}
}
The next are the classes involved in the test:
import com.espn.csemobile.espnapp.models.clubhouse.*
import com.espn.csemobile.espnapp.services.clubhouse.contexts.ClubhouseContext
import com.espn.csemobile.espnapp.services.core.CoreService
import org.springframework.context.annotation.Primary
import org.springframework.context.annotation.ScopedProxyMode
import org.springframework.stereotype.Service
import org.springframework.web.context.annotation.RequestScope
import rx.Single
interface ClubhouseService {
fun getClubhouseFor(context: ClubhouseContext): Single<Clubhouse?>
}
#Service
#RequestScope(proxyMode = ScopedProxyMode.NO)
#Primary
class ClubhouseServiceImpl(private val clubhouseContext: ClubhouseContext,
private var staticClubhouseService: StaticClubhouseService,
private var automatedClubhouseService: AutomatedClubhouseService,
private val coreService: CoreService?): ClubhouseService {
override fun getClubhouseFor(context: ClubhouseContext): Single<Clubhouse?> {
return staticClubhouseService.getClubhouseFor(clubhouseContext).flatMap { clubhouse ->
if (clubhouse != null) return#flatMap Single.just(clubhouse)
return#flatMap automatedClubhouseService.getClubhouseFor(clubhouseContext)
}
}
}
Well, first of all GroovySpy or GroovyStub do not make sense for Java or Kotlin classes because the special features of Groovy mocks are only available for Groovy classes. So don't expect to be able to mock constructors or static methods that way, if that was the reason for the usage. This is also documented here:
When Should Groovy Mocks be Favored over Regular Mocks? Groovy mocks should be used when the code under specification is written in Groovy and some of the unique Groovy mock features are needed. When called from Java code, Groovy mocks will behave like regular mocks. Note that it isn’t necessary to use a Groovy mock merely because the code under specification and/or mocked type is written in Groovy. Unless you have a concrete reason to use a Groovy mock, prefer a regular mock.
As for your problem with the spy, you cannot use a spy on an interface type. This is documented here:
A spy is always based on a real object. Hence you must provide a class type rather than an interface type, along with any constructor arguments for the type.
So either you just switch to Mock or Stub, both of which work on interface types, or you spy on the implementation class instead. In any case, my main suggestion is to read the documentation first and then try to use a new tool like Spock. My impression is that you have not used Spock before, but of course I could be wrong.

Something missing using micronaut filter

I am attempting to implement a filter in a micronaut microservice, using the example code documented in Section 6.18 of the documentation:
https://docs.micronaut.io/latest/guide/index.html#filters
I have a HelloWord service that is essentially the same as the service provided on the documentation, with a controller that goes to "/hello" (as documented). I am also using the same TraceService and trace filter that is provided in Section 6.18. I am compiling and running the server without problems.
Unfortunately, the filter is not being engaged when I test the microservice.
I am pretty sure that something is missing in my code, but as I said I am using the same code that is in the example:
TraceService Class
import io.micronaut.http.HttpRequest;
import io.reactivex.Flowable;
import io.reactivex.schedulers.Schedulers;
import org.slf4j.*;
import javax.inject.Singleton;
#Singleton
public class TraceService {
private static final Logger LOG = LoggerFactory.getLogger(TraceService.class);
Flowable<Boolean> trace(HttpRequest<?> request) {
System.out.println("TRACE ENGAGED!");
return Flowable.fromCallable(() -> {
if (LOG.isDebugEnabled()) {
LOG.debug("Tracing request: " + request.getUri());
}
// trace logic here, potentially performing I/O
return true;
}).subscribeOn(Schedulers.io());
}
}
Trace Filter
import io.micronaut.http.*;
import io.micronaut.http.annotation.Filter;
import io.micronaut.http.filter.*;
import org.reactivestreams.Publisher;
#Filter("/hello/**")
public class TraceFilter implements HttpServerFilter {
private final TraceService traceService;
public TraceFilter(TraceService traceService) {
System.out.println("Filter created!");
this.traceService = traceService;
}
#Override
public Publisher<MutableHttpResponse<?>> doFilter(HttpRequest<?> request, ServerFilterChain chain) {
System.out.println("Filter engaged!");
return traceService.trace(request)
.switchMap(aBoolean -> chain.proceed(request))
.doOnNext(res -> res.getHeaders().add("X-Trace-Enabled", "true")
);
}
}
The Controller
import io.micronaut.http.annotation.*;
#Controller("/hello")
public class HelloController {
#Get("/")
public String index() {
return "Hello World";
}
}
Note that the controller uses code from Section 2.2 of the documentation:
https://docs.micronaut.io/latest/guide/index.html#creatingServer
I did a number of things to try and see what was happening with the filter, including putting little printouts in strategic parts of the Service and the filter. These printouts are not printing out, which tells me that the filter is not being created or used by Micronaut.
Clearly I am missing somethning. I suspect that there is something I need to do in order to get the system to engage the filter. Unfortunately the documentation just tells how to make the filter, not how to use it in the microservice. Furthermore, there don't appear to be any complete code examples that tell how to make the request system utilize the filter (maybe there is an annotation I need to add to the controller???).
Could someone tell me what I am missing? How do I get the filter to work? At the very least, could someone provide a complete example of how to create the filter and use it in an actual microservice?
Problem solved.
It actually helps a great deal if one puts the filter and service files in the right place. It was late when I made the files and I put them in the test area, not the development area. Once placed in the right place, the filter was properly injected into the microservice.
Sorry for the waste of space here, folks. Is there any way a poster can delete an embarrassing post?

Resources